From 8f3943557ce67911811727da1f80e004e328acfa Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 29 Apr 2024 12:55:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DOracle=E4=B8=8B=E7=89=B9?= =?UTF-8?q?=E6=AE=8A=E8=A1=A8=E5=90=8D=E5=AF=BC=E8=87=B4meta=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E8=8E=B7=E5=8F=96=E4=B8=8D=E5=88=B0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/dromara/hutool/db/DbUtil.java | 26 -- .../org/dromara/hutool/db/meta/MetaUtil.java | 293 +++++++++++++----- .../org/dromara/hutool/db/meta/Table.java | 44 ++- .../dromara/hutool/db/IssueI9BANETest.java | 8 +- 4 files changed, 262 insertions(+), 109 deletions(-) delete mode 100644 hutool-db/src/main/java/org/dromara/hutool/db/DbUtil.java diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/DbUtil.java b/hutool-db/src/main/java/org/dromara/hutool/db/DbUtil.java deleted file mode 100644 index 6fda89292..000000000 --- a/hutool-db/src/main/java/org/dromara/hutool/db/DbUtil.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2023 looly(loolly@aliyun.com) - * Hutool is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * https://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -package org.dromara.hutool.db; - -import org.dromara.hutool.core.convert.Convert; -import org.dromara.hutool.db.config.DSKeys; -import org.dromara.hutool.log.level.Level; -import org.dromara.hutool.setting.Setting; - -/** - * 数据库操作工具类 - * - * @author Looly - */ -public final class DbUtil { -} diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/org/dromara/hutool/db/meta/MetaUtil.java index 140b70eed..2309f76a1 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/meta/MetaUtil.java @@ -16,15 +16,13 @@ import org.dromara.hutool.core.collection.ListUtil; import org.dromara.hutool.core.convert.Convert; import org.dromara.hutool.core.io.IoUtil; import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.db.DbException; import org.dromara.hutool.db.Entity; import javax.sql.DataSource; import java.sql.*; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * 数据库元数据信息工具类 @@ -214,79 +212,59 @@ public class MetaUtil { * @return Table对象 * @since 5.7.22 */ - public static Table getTableMeta(final DataSource ds, String catalog, String schema, final String tableName) { - final Table table = Table.of(tableName); + public static Table getTableMeta(final DataSource ds, final String catalog, final String schema, final String tableName) { Connection conn = null; try { conn = ds.getConnection(); - - // catalog和schema获取失败默认使用null代替 - if (null == catalog) { - catalog = getCatalog(conn); - } - table.setCatalog(catalog); - if (null == schema) { - schema = getSchema(conn); - } - table.setSchema(schema); - - final DatabaseMetaData metaData = conn.getMetaData(); - - // 获得表元数据(表注释) - try (final ResultSet rs = metaData.getTables(catalog, schema, tableName, new String[]{TableType.TABLE.value()})) { - if (null != rs) { - if (rs.next()) { - table.setComment(rs.getString("REMARKS")); - } - } - } - - // 获得主键 - try (final ResultSet rs = metaData.getPrimaryKeys(catalog, schema, tableName)) { - if (null != rs) { - while (rs.next()) { - table.addPk(rs.getString("COLUMN_NAME")); - } - } - } - - // 获得列 - try (final ResultSet rs = metaData.getColumns(catalog, schema, tableName, null)) { - if (null != rs) { - while (rs.next()) { - table.setColumn(Column.of(table, rs)); - } - } - } - - // 获得索引信息(since 5.7.23) - try (final ResultSet rs = metaData.getIndexInfo(catalog, schema, tableName, false, false)) { - final Map indexInfoMap = new LinkedHashMap<>(); - if (null != rs) { - while (rs.next()) { - //排除tableIndexStatistic类型索引 - if (0 == rs.getShort("TYPE")) { - continue; - } - - final String indexName = rs.getString("INDEX_NAME"); - final String key = StrUtil.join("&", tableName, indexName); - // 联合索引情况下一个索引会有多个列,此处须组合索引列到一个索引信息对象下 - IndexInfo indexInfo = indexInfoMap.get(key); - if (null == indexInfo) { - indexInfo = new IndexInfo(rs.getBoolean("NON_UNIQUE"), indexName, tableName, schema, catalog); - indexInfoMap.put(key, indexInfo); - } - indexInfo.getColumnIndexInfoList().add(ColumnIndexInfo.of(rs)); - } - } - table.setIndexInfoList(ListUtil.of(indexInfoMap.values())); - } - } catch (final SQLException e) { - throw new DbException("Get columns error!", e); + return getTableMeta(conn, catalog, schema, tableName); + } catch (final SQLException e){ + throw new DbException(e); } finally { IoUtil.closeQuietly(conn); } + } + + /** + * 获得表的元信息
+ * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + *
+	 *     remarks = true
+	 *     useInformationSchema = true
+	 * 
+ * + * @param conn 数据库连接对象,使用结束后不会关闭。 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCatalog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.8.28 + */ + public static Table getTableMeta(final Connection conn, String catalog, String schema, final String tableName) { + final Table table = Table.of(tableName); + + // catalog和schema获取失败默认使用null代替 + if (null == catalog) { + catalog = getCatalog(conn); + } + table.setCatalog(catalog); + if (null == schema) { + schema = getSchema(conn); + } + table.setSchema(schema); + + final DatabaseMetaData metaData = getMetaData(conn); + // 获取原始表名 + final String pureTableName = unWrapIfOracle(metaData, tableName); + table.setPureTableName(pureTableName); + // 获得表元数据(表注释) + table.setRemarks(getRemarks(metaData, catalog, schema, pureTableName)); + // 获得主键 + table.setPkNames(getPrimaryKeys(metaData, catalog, schema, pureTableName)); + // 获得列 + fetchColumns(metaData, catalog, schema, table); + // 获得索引信息(since 5.7.23) + final Map indexInfoMap = getIndexInfo(metaData, catalog, schema, tableName); + table.setIndexInfoList(ListUtil.of(indexInfoMap.values())); return table; } @@ -330,4 +308,177 @@ public class MetaUtil { return null; } + + /** + * 获取数据库连接的元数据信息。 + * + * @param conn 数据库连接对象。如果连接为null,则返回null。 + * @return DatabaseMetaData 数据库元数据对象,如果获取失败或连接为null,则返回null。 + * @since 5.8.28 + */ + public static DatabaseMetaData getMetaData(final Connection conn) { + if (null == conn) { + return null; + } + try { + return conn.getMetaData(); + } catch (final SQLException e) { + // ignore + } + + return null; + } + + /** + * 获取指定表的备注信息。 + * + * @param metaData 数据库元数据,用于查询表信息。 + * @param catalog 目录名称,用于指定查询的数据库(可为{@code null},表示任意目录)。 + * @param schema 方案名称,用于指定表所属的schema(可为{@code null},表示任意schema)。 + * @param tableName 表名称,指定要查询备注信息的表。 + * @return 表的备注信息。未找到指定的表或查询成功但无结果,则返回null。 + * @since 5.8.28 + */ + public static String getRemarks(final DatabaseMetaData metaData, final String catalog, final String schema, String tableName) { + // issue#I9BANE Oracle中特殊表名需要解包 + tableName = unWrapIfOracle(metaData, tableName); + + try (final ResultSet rs = metaData.getTables(catalog, schema, tableName, new String[]{TableType.TABLE.value()})) { + if (null != rs) { + if (rs.next()) { + return rs.getString("REMARKS"); + } + } + } catch (final SQLException e) { + throw new DbException(e); + } + // 未找到指定的表或查询成功但无结果 + return null; + } + + /** + * 获取指定表的主键列名列表。 + * + * @param metaData 数据库元数据,用于查询主键信息。 + * @param catalog 数据库目录,用于限定查询范围。 + * @param schema 数据库模式,用于限定查询范围。 + * @param tableName 表名,指定要查询主键的表。 + * @return 主键列名的列表。如果表没有主键,则返回空列表。 + * @throws DbException 如果查询过程中发生SQLException,将抛出DbException。 + * @since 5.8.28 + */ + public static Set getPrimaryKeys(final DatabaseMetaData metaData, final String catalog, final String schema, String tableName) { + // issue#I9BANE Oracle中特殊表名需要解包 + tableName = unWrapIfOracle(metaData, tableName); + + // 初始化主键列表 + Set primaryKeys = null; + try (final ResultSet rs = metaData.getPrimaryKeys(catalog, schema, tableName)) { + // 如果结果集不为空,遍历结果集获取主键列名 + if (null != rs) { + primaryKeys = new LinkedHashSet<>(rs.getFetchSize(), 1); + while (rs.next()) { + primaryKeys.add(rs.getString("COLUMN_NAME")); + } + } + } catch (final SQLException e) { + // 将SQLException转换为自定义的DbException抛出 + throw new DbException(e); + } + return primaryKeys; + } + + /** + * 获取指定表的索引信息。 + * + * @param metaData 数据库元数据,用于查询索引信息。 + * @param catalog 数据库目录,用于限定查询范围。 + * @param schema 数据库模式,用于限定查询范围。 + * @param tableName 需要查询索引信息的表名。 + * @return 返回一个映射,其中包含表的索引信息。键是表名和索引名的组合,值是索引信息对象。 + * @since 5.8.28 + */ + public static Map getIndexInfo(final DatabaseMetaData metaData, final String catalog, final String schema, final String tableName) { + final Map indexInfoMap = new LinkedHashMap<>(); + + try (final ResultSet rs = metaData.getIndexInfo(catalog, schema, tableName, false, false)) { + if (null != rs) { + while (rs.next()) { + //排除统计(tableIndexStatistic)类型索引 + if (0 == rs.getShort("TYPE")) { + continue; + } + + final String indexName = rs.getString("INDEX_NAME"); + final String key = StrUtil.join("&", tableName, indexName); + // 联合索引情况下一个索引会有多个列,此处须组合索引列到一个索引信息对象下 + IndexInfo indexInfo = indexInfoMap.get(key); + if (null == indexInfo) { + indexInfo = new IndexInfo(rs.getBoolean("NON_UNIQUE"), indexName, tableName, schema, catalog); + indexInfoMap.put(key, indexInfo); + } + indexInfo.getColumnIndexInfoList().add(ColumnIndexInfo.of(rs)); + } + } + } catch (final SQLException e) { + throw new DbException(e); + } + return indexInfoMap; + } + + /** + * 判断当前数据库是否为Oracle。 + * + * @param metaData 数据库元数据,用于获取数据库产品名称。 + * @return 返回true表示当前数据库是Oracle,否则返回false。 + * @throws DbException 如果获取数据库产品名称时发生SQLException,将抛出DbException。 + * @since 5.8.28 + */ + public static boolean isOracle(final DatabaseMetaData metaData) throws DbException { + try { + return StrUtil.equalsIgnoreCase("Oracle", metaData.getDatabaseProductName()); + } catch (final SQLException e) { + throw new DbException(e); + } + } + + /** + * 如果是在Oracle数据库中并且表名被双引号包裹,则移除这些引号。 + * + * @param metaData 数据库元数据,用于判断是否为Oracle数据库。 + * @param tableName 待处理的表名,可能被双引号包裹。 + * @return 处理后的表名,如果原表名被双引号包裹且是Oracle数据库,则返回去除了双引号的表名;否则返回原表名。 + */ + private static String unWrapIfOracle(final DatabaseMetaData metaData, String tableName) { + final char wrapChar = '"'; + // 判断表名是否被双引号包裹且当前数据库为Oracle,如果是,则移除双引号 + if (StrUtil.isWrap(tableName, wrapChar) && isOracle(metaData)) { + tableName = StrUtil.unWrap(tableName, wrapChar); + } + return tableName; + } + + /** + * 从数据库元数据中获取指定表的列信息。 + * + * @param metaData 数据库元数据,用于查询列信息。 + * @param catalog 数据库目录,用于过滤列信息。 + * @param schema 数据库模式,用于过滤列信息。 + * @param table 表对象,用于存储获取到的列信息。 + */ + private static void fetchColumns(final DatabaseMetaData metaData, final String catalog, final String schema, final Table table) { + // issue#I9BANE Oracle中特殊表名需要解包 + final String tableName = unWrapIfOracle(metaData, ObjUtil.defaultIfNull(table.getPureTableName(), table::getTableName)); + + // 获得列 + try (final ResultSet rs = metaData.getColumns(catalog, schema, tableName, null)) { + if (null != rs) { + while (rs.next()) { + table.addColumn(Column.of(table, rs)); + } + } + } catch (final SQLException e) { + throw new DbException(e); + } + } } diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/meta/Table.java b/hutool-db/src/main/java/org/dromara/hutool/db/meta/Table.java index 439f06eee..c166080bc 100644 --- a/hutool-db/src/main/java/org/dromara/hutool/db/meta/Table.java +++ b/hutool-db/src/main/java/org/dromara/hutool/db/meta/Table.java @@ -37,13 +37,17 @@ public class Table implements Serializable, Cloneable { */ private String catalog; /** - * 表名 + * 表名,特殊表名一般带包装符号,如:"1234" */ private String tableName; + /** + * 表名(无包装符号),如"1234"对应的pureTableName为1234 + */ + private String pureTableName; /** * 注释 */ - private String comment; + private String remarks; /** * 主键字段名列表 */ @@ -57,6 +61,12 @@ public class Table implements Serializable, Cloneable { */ private final Map columns = new LinkedHashMap<>(); + /** + * 根据提供的表名创建一个新的Table实例。 + * + * @param tableName 表的名称,用于标识数据库中的特定表。 + * @return 返回一个新的Table实例,其名称为传入的表名。 + */ public static Table of(final String tableName) { return new Table(tableName); } @@ -137,23 +147,41 @@ public class Table implements Serializable, Cloneable { this.tableName = tableName; } + /** + * 获取表名(无包装符号),如"1234"对应的pureTableName为1234 + * + * @return 表名(无包装符号) + */ + public String getPureTableName() { + return pureTableName; + } + + /** + * 设置表名(无包装符号),如"1234"对应的pureTableName为1234 + * + * @param pureTableName 表名 + */ + public void setPureTableName(final String pureTableName) { + this.pureTableName = pureTableName; + } + /** * 获取注释 * * @return 注释 */ - public String getComment() { - return comment; + public String getRemarks() { + return remarks; } /** * 设置注释 * - * @param comment 注释 + * @param remarks 注释 * @return this */ - public Table setComment(final String comment) { - this.comment = comment; + public Table setRemarks(final String remarks) { + this.remarks = remarks; return this; } @@ -193,7 +221,7 @@ public class Table implements Serializable, Cloneable { * @param column 列对象 * @return 自己 */ - public Table setColumn(final Column column) { + public Table addColumn(final Column column) { this.columns.put(column.getName(), column); return this; } diff --git a/hutool-db/src/test/java/org/dromara/hutool/db/IssueI9BANETest.java b/hutool-db/src/test/java/org/dromara/hutool/db/IssueI9BANETest.java index 86cebffd1..24d1f8d1b 100644 --- a/hutool-db/src/test/java/org/dromara/hutool/db/IssueI9BANETest.java +++ b/hutool-db/src/test/java/org/dromara/hutool/db/IssueI9BANETest.java @@ -12,11 +12,11 @@ public class IssueI9BANETest { @Test @Disabled void metaTest() { - final Db db = Db.of("orcl"); - db.find(Entity.of("\"1234\"")); - final DSWrapper ds = DSUtil.getDS("orcl"); final Table tableMeta = MetaUtil.getTableMeta(ds, null, null, "\"1234\""); - Console.log(tableMeta.getIndexInfoList()); + Console.log("remarks: " + tableMeta.getRemarks()); + Console.log("pks: " + tableMeta.getPkNames()); + Console.log("columns: " + tableMeta.getColumns()); + Console.log("index: " + tableMeta.getIndexInfoList()); } }