From 6d65106b802b2278c54d659a16f8686bff619d84 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 29 Apr 2024 13:31:56 +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 --- .../db/meta/DatabaseMetaDataWrapper.java | 246 ++++++++++++++++++ .../org/dromara/hutool/db/meta/MetaUtil.java | 123 ++------- 2 files changed, 263 insertions(+), 106 deletions(-) create mode 100644 hutool-db/src/main/java/org/dromara/hutool/db/meta/DatabaseMetaDataWrapper.java diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/meta/DatabaseMetaDataWrapper.java b/hutool-db/src/main/java/org/dromara/hutool/db/meta/DatabaseMetaDataWrapper.java new file mode 100644 index 000000000..c202e7eaa --- /dev/null +++ b/hutool-db/src/main/java/org/dromara/hutool/db/meta/DatabaseMetaDataWrapper.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2024. 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.meta; + +import org.dromara.hutool.core.lang.wrapper.SimpleWrapper; +import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.core.util.ObjUtil; +import org.dromara.hutool.db.DbException; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +/** + * 用于封装DatabaseMetaData对象,并提供特定数据库的元数据访问。 + * 这个类提供了一种方便的方式来访问和操作数据库元数据,是对DatabaseMetaData的简单封装。 + * + * @author Looly + * @since 6.0.0 + */ +public class DatabaseMetaDataWrapper extends SimpleWrapper { + + /** + * 创建一个 DatabaseMetaDataWrapper 实例。 + * + * @param conn 数据库连接 + * @return 返回一个新的 DatabaseMetaDataWrapper 实例。 + */ + public static DatabaseMetaDataWrapper of(final Connection conn) { + return of(MetaUtil.getMetaData(conn), MetaUtil.getCatalog(conn), MetaUtil.getSchema(conn)); + } + + /** + * 创建一个 DatabaseMetaDataWrapper 实例。 + * + * @param raw 原始的 DatabaseMetaData 对象,这是 Java SQL API 的一部分,用于获取数据库元数据信息。 + * @param catalog 要使用的数据库目录(schema)的名称。可以为{@code null},具体行为取决于数据库和实现。 + * @param schema 要使用的数据库模式(schema)的名称。可以为{@code null},具体行为取决于数据库和实现。 + * @return 返回一个新的 DatabaseMetaDataWrapper 实例。 + */ + public static DatabaseMetaDataWrapper of(final DatabaseMetaData raw, final String catalog, final String schema) { + return new DatabaseMetaDataWrapper(raw, catalog, schema); + } + + private final String catalog; + private final String schema; + private final boolean isOracle; + + /** + * 构造。这个包装类用于封装DatabaseMetaData对象,并提供特定数据库的元数据访问。 + * + * @param raw 原始的DatabaseMetaData对象,这是Java SQL API的一部分,用于获取数据库元数据。 + * @param catalog 要使用的数据库目录(在某些数据库系统中相当于数据库名称)。 + * @param schema 要使用的数据库模式(在某些数据库系统中相当于命名空间)。 + */ + public DatabaseMetaDataWrapper(final DatabaseMetaData raw, final String catalog, final String schema) { + super(raw); // 调用父类构造函数,将原始DatabaseMetaData对象传递给父类。 + this.catalog = catalog; + this.schema = schema; + // 检查是否为Oracle数据库,用于后续提供特定于Oracle的元数据支持 + this.isOracle = MetaUtil.isOracle(raw); + } + + /** + * 是否为Oracle数据库 + * + * @return 是否为Oracle数据库 + */ + public boolean isOracle() { + return isOracle; + } + + /** + * 获取数据库类型名称 + * + * @return 数据库类型名称 + */ + public String getProductName() { + try { + return raw.getDatabaseProductName(); + } catch (final SQLException e) { + throw new DbException(e); + } + } + + /** + * 获取数据库驱动名称 + * + * @return 数据库驱动名称 + */ + public String getDriverName() { + try { + return raw.getDriverName(); + } catch (final SQLException e) { + throw new DbException(e); + } + } + + /** + * 获取指定表的备注信息。 + * + * @param tableName 表名称,指定要查询备注信息的表。 + * @return 表的备注信息。未找到指定的表或查询成功但无结果,则返回null。 + */ + public String getRemarks(String tableName) { + final String catalog = this.catalog; + final String schema = this.schema; + + // issue#I9BANE Oracle中特殊表名需要解包 + tableName = getPureTableName(tableName); + + try (final ResultSet rs = this.raw.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 tableName 表名,指定要查询主键的表。 + * @return 主键列名的列表。如果表没有主键,则返回空列表。 + * @throws DbException 如果查询过程中发生SQLException,将抛出DbException。 + * @since 5.8.28 + */ + public Set getPrimaryKeys(String tableName) { + final String catalog = this.catalog; + final String schema = this.schema; + // issue#I9BANE Oracle中特殊表名需要解包 + tableName = getPureTableName(tableName); + + // 初始化主键列表 + Set primaryKeys = null; + try (final ResultSet rs = this.raw.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 tableName 需要查询索引信息的表名。 + * @return 返回一个映射,其中包含表的索引信息。键是表名和索引名的组合,值是索引信息对象。 + * @since 5.8.28 + */ + public Map getIndexInfo(final String tableName) { + final String catalog = this.catalog; + final String schema = this.schema; + final Map indexInfoMap = new LinkedHashMap<>(); + + try (final ResultSet rs = this.raw.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; + } + + /** + * 从数据库元数据中获取指定表的列信息。 + * + * @param table 表对象,用于存储获取到的列信息。 + */ + public void fetchColumns(final Table table) { + final String catalog = this.catalog; + final String schema = this.schema; + + // issue#I9BANE Oracle中特殊表名需要解包 + final String tableName = getPureTableName(ObjUtil.defaultIfNull(table.getPureTableName(), table::getTableName)); + + // 获得列 + try (final ResultSet rs = this.raw.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); + } + } + + /** + * 如果是在Oracle数据库中并且表名被双引号包裹,则移除这些引号。 + * + * @param tableName 待处理的表名,可能被双引号包裹。 + * @return 处理后的表名,如果原表名被双引号包裹且是Oracle数据库,则返回去除了双引号的表名;否则返回原表名。 + */ + public String getPureTableName(String tableName) { + final char wrapChar = '"'; + // 判断表名是否被双引号包裹且当前数据库为Oracle,如果是,则移除双引号 + if (StrUtil.isWrap(tableName, wrapChar) && isOracle) { + tableName = StrUtil.unWrap(tableName, wrapChar); + } + return tableName; + } +} 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 2309f76a1..bef3dad1a 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,13 +16,15 @@ 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.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * 数据库元数据信息工具类 @@ -253,17 +255,20 @@ public class MetaUtil { table.setSchema(schema); final DatabaseMetaData metaData = getMetaData(conn); + final DatabaseMetaDataWrapper metaDataWrapper = DatabaseMetaDataWrapper.of(metaData, catalog, schema); + // 获取原始表名 - final String pureTableName = unWrapIfOracle(metaData, tableName); + final String pureTableName = metaDataWrapper.getPureTableName(tableName); table.setPureTableName(pureTableName); + // 获得表元数据(表注释) - table.setRemarks(getRemarks(metaData, catalog, schema, pureTableName)); + table.setRemarks(metaDataWrapper.getRemarks(pureTableName)); // 获得主键 - table.setPkNames(getPrimaryKeys(metaData, catalog, schema, pureTableName)); + table.setPkNames(metaDataWrapper.getPrimaryKeys(pureTableName)); // 获得列 - fetchColumns(metaData, catalog, schema, table); + metaDataWrapper.fetchColumns(table); // 获得索引信息(since 5.7.23) - final Map indexInfoMap = getIndexInfo(metaData, catalog, schema, tableName); + final Map indexInfoMap = metaDataWrapper.getIndexInfo(tableName); table.setIndexInfoList(ListUtil.of(indexInfoMap.values())); return table; @@ -339,21 +344,8 @@ public class MetaUtil { * @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; + public static String getRemarks(final DatabaseMetaData metaData, final String catalog, final String schema, final String tableName) { + return DatabaseMetaDataWrapper.of(metaData, catalog, schema).getRemarks(tableName); } /** @@ -367,25 +359,8 @@ public class MetaUtil { * @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; + public static Set getPrimaryKeys(final DatabaseMetaData metaData, final String catalog, final String schema, final String tableName) { + return DatabaseMetaDataWrapper.of(metaData, catalog, schema).getPrimaryKeys(tableName); } /** @@ -399,31 +374,7 @@ public class MetaUtil { * @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; + return DatabaseMetaDataWrapper.of(metaData, catalog, schema).getIndexInfo(tableName); } /** @@ -441,44 +392,4 @@ public class MetaUtil { 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); - } - } }