修复Oracle下特殊表名导致meta信息获取不到问题

This commit is contained in:
Looly 2024-04-29 12:55:07 +08:00
parent 4ce3926c9c
commit 8f3943557c
4 changed files with 262 additions and 109 deletions

View File

@ -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 {
}

View File

@ -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<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.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);
}
}
/**
* 获得表的元信息<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.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<String, IndexInfo> 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<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 DbException(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.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);
}
}
}

View File

@ -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<String, Column> 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;
}

View File

@ -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());
}
}