mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-05 17:37:59 +08:00
修复Oracle下特殊表名导致meta信息获取不到问题
This commit is contained in:
parent
cce1b9aaa1
commit
768e890e2c
@ -2,7 +2,7 @@
|
||||
# 🚀Changelog
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.8.28(2024-04-28)
|
||||
# 5.8.28(2024-04-29)
|
||||
|
||||
### 🐣新特性
|
||||
* 【core 】 修正XmlUtil的omitXmlDeclaration描述注释(issue#I9CPC7@Gitee)
|
||||
@ -28,6 +28,7 @@
|
||||
* 【core 】 修复FileUtil.copyFile没有创建父目录导致的问题(issue#3557@Github)
|
||||
* 【http 】 修复HttpDownloader全局超时无效问题(issue#3556@Github)
|
||||
* 【core 】 修复ZipReader.checkZipBomb遇到空目录报错问题(issue#I9K494@Gitee)
|
||||
* 【db 】 修复Oracle下特殊表名导致meta信息获取不到问题(issue#I9BANE@Gitee)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.8.27(2024-03-29)
|
||||
|
@ -2,21 +2,15 @@ package cn.hutool.db.meta;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.db.DbRuntimeException;
|
||||
import cn.hutool.db.DbUtil;
|
||||
import cn.hutool.db.Entity;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 数据库元数据信息工具类
|
||||
@ -207,78 +201,57 @@ public class MetaUtil {
|
||||
* @since 5.7.22
|
||||
*/
|
||||
public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) {
|
||||
final Table table = Table.create(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.create(table, rs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获得索引信息(since 5.7.23)
|
||||
try (final ResultSet rs = metaData.getIndexInfo(catalog, schema, tableName, false, false)) {
|
||||
final Map<String, IndexInfo> 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.create(rs));
|
||||
}
|
||||
}
|
||||
table.setIndexInfoList(ListUtil.toList(indexInfoMap.values()));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DbRuntimeException("Get columns error!", e);
|
||||
return getTableMeta(conn, catalog, schema, tableName);
|
||||
} catch (final SQLException e){
|
||||
throw new DbRuntimeException(e);
|
||||
} finally {
|
||||
DbUtil.close(conn);
|
||||
IoUtil.close(conn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得表的元信息<br>
|
||||
* 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加:
|
||||
* <pre>
|
||||
* remarks = true
|
||||
* useInformationSchema = true
|
||||
* </pre>
|
||||
*
|
||||
* @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.create(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.setComment(getRemarks(metaData, catalog, schema, pureTableName));
|
||||
// 获得主键
|
||||
table.setPkNames(getPrimaryKeys(metaData, catalog, schema, pureTableName));
|
||||
// 获得列
|
||||
fetchColumns(metaData, catalog, schema, table);
|
||||
// 获得索引信息(since 5.7.23)
|
||||
final Map<String, IndexInfo> indexInfoMap = getIndexInfo(metaData, catalog, schema, tableName);
|
||||
table.setIndexInfoList(ListUtil.toList(indexInfoMap.values()));
|
||||
|
||||
return table;
|
||||
}
|
||||
@ -335,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 DbRuntimeException(e);
|
||||
}
|
||||
// 未找到指定的表或查询成功但无结果
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定表的主键列名列表。
|
||||
*
|
||||
* @param metaData 数据库元数据,用于查询主键信息。
|
||||
* @param catalog 数据库目录,用于限定查询范围。
|
||||
* @param schema 数据库模式,用于限定查询范围。
|
||||
* @param tableName 表名,指定要查询主键的表。
|
||||
* @return 主键列名的列表。如果表没有主键,则返回空列表。
|
||||
* @throws DbRuntimeException 如果查询过程中发生SQLException,将抛出DbRuntimeException。
|
||||
* @since 5.8.28
|
||||
*/
|
||||
public static Set<String> getPrimaryKeys(final DatabaseMetaData metaData, final String catalog, final String schema, String tableName) {
|
||||
// issue#I9BANE Oracle中特殊表名需要解包
|
||||
tableName = unWrapIfOracle(metaData, tableName);
|
||||
|
||||
// 初始化主键列表
|
||||
Set<String> 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 DbRuntimeException(e);
|
||||
}
|
||||
return primaryKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定表的索引信息。
|
||||
*
|
||||
* @param metaData 数据库元数据,用于查询索引信息。
|
||||
* @param catalog 数据库目录,用于限定查询范围。
|
||||
* @param schema 数据库模式,用于限定查询范围。
|
||||
* @param tableName 需要查询索引信息的表名。
|
||||
* @return 返回一个映射,其中包含表的索引信息。键是表名和索引名的组合,值是索引信息对象。
|
||||
* @since 5.8.28
|
||||
*/
|
||||
public static Map<String, IndexInfo> getIndexInfo(final DatabaseMetaData metaData, final String catalog, final String schema, final String tableName) {
|
||||
final Map<String, IndexInfo> 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.create(rs));
|
||||
}
|
||||
}
|
||||
} catch (final SQLException e) {
|
||||
throw new DbRuntimeException(e);
|
||||
}
|
||||
return indexInfoMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前数据库是否为Oracle。
|
||||
*
|
||||
* @param metaData 数据库元数据,用于获取数据库产品名称。
|
||||
* @return 返回true表示当前数据库是Oracle,否则返回false。
|
||||
* @throws DbRuntimeException 如果获取数据库产品名称时发生SQLException,将抛出DbRuntimeException。
|
||||
* @since 5.8.28
|
||||
*/
|
||||
public static boolean isOracle(final DatabaseMetaData metaData) throws DbRuntimeException {
|
||||
try {
|
||||
return StrUtil.equalsIgnoreCase("Oracle", metaData.getDatabaseProductName());
|
||||
} catch (final SQLException e) {
|
||||
throw new DbRuntimeException(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, table.getTableName());
|
||||
|
||||
// 获得列
|
||||
try (final ResultSet rs = metaData.getColumns(catalog, schema, tableName, null)) {
|
||||
if (null != rs) {
|
||||
while (rs.next()) {
|
||||
table.setColumn(Column.create(table, rs));
|
||||
}
|
||||
}
|
||||
} catch (final SQLException e) {
|
||||
throw new DbRuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,9 @@ public class IssueI9BANETest {
|
||||
|
||||
final DataSource ds = DSFactory.get("orcl");
|
||||
final Table tableMeta = MetaUtil.getTableMeta(ds, null, null, "\"1234\"");
|
||||
Console.log(tableMeta.getIndexInfoList());
|
||||
Console.log("remarks: " + tableMeta.getComment());
|
||||
Console.log("pks: " + tableMeta.getPkNames());
|
||||
Console.log("columns: " + tableMeta.getColumns());
|
||||
Console.log("index: " + tableMeta.getIndexInfoList());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user