add TomlConfigParser

This commit is contained in:
Looly 2025-10-28 01:12:01 +08:00
parent 1f5e574974
commit 8ae9643377
4 changed files with 390 additions and 1 deletions

View File

@ -23,7 +23,7 @@ import java.util.Iterator;
* 参考<a href="http://stackoverflow.com/a/21791059/6030888">http://stackoverflow.com/a/21791059/6030888</a>
*
* @author Looly
* @param str
* @param str 字符串
*/
public record CodePointIter(String str) implements Iterable<Integer> {

View File

@ -0,0 +1,237 @@
package cn.hutool.v7.db.config;
import cn.hutool.v7.core.array.ArrayUtil;
import cn.hutool.v7.core.convert.ConvertUtil;
import cn.hutool.v7.core.io.resource.NoResourceException;
import cn.hutool.v7.core.io.resource.ResourceUtil;
import cn.hutool.v7.core.text.StrUtil;
import cn.hutool.v7.core.util.ObjUtil;
import cn.hutool.v7.db.DbException;
import cn.hutool.v7.db.driver.DriverUtil;
import cn.hutool.v7.db.sql.SqlLog;
import cn.hutool.v7.db.sql.filter.SqlLogFilter;
import cn.hutool.v7.log.level.Level;
import cn.hutool.v7.setting.toml.TomlReader;
import java.util.HashMap;
import java.util.Map;
/**
* 基于TOML类型的数据库配置解析器
*
* @author Looly
* @since 7.0.0
*/
public class TomlConfigParser implements ConfigParser {
private static final String CONNECTION_PREFIX = "connection.";
/**
* 默认TOML配置文件路径
*/
private static final String[] DEFAULT_DB_TOML_PATHS = {"config/db.toml", "db.toml"};
/**
* 创建默认配置解析器
*
* @return TomlConfigParser
*/
public static TomlConfigParser of() {
return of(null);
}
/**
* 创建配置解析器
*
* @param tomlPath TOML配置文件路径
* @return TomlConfigParser
*/
public static TomlConfigParser of(final String tomlPath) {
return new TomlConfigParser(tomlPath);
}
private final Map<String, Object> tomlData;
/**
* 构造函数
*
* @param tomlPath 自定义TOML配置文件路径{@code null}表示使用默认路径
*/
public TomlConfigParser(final String tomlPath) {
String tomlContent = null;
if (null != tomlPath) {
// 读取指定TOML文件
tomlContent = ResourceUtil.readUtf8Str(tomlPath);
} else {
// 读取默认TOML文件
for (String defaultDbTomlPath : DEFAULT_DB_TOML_PATHS) {
try {
tomlContent = ResourceUtil.readUtf8Str(defaultDbTomlPath);
} catch (NoResourceException e) {
// ignore
}
}
}
if (null == tomlContent) {
throw new NoResourceException("Default db toml [{}] in classpath not found !", ArrayUtil.join(DEFAULT_DB_TOML_PATHS, ","));
}
this.tomlData = new TomlReader(tomlContent, false).read();
}
@SuppressWarnings("unchecked")
@Override
public DbConfig parse(final String group) {
Map<String, Object> groupData;
if (StrUtil.isEmpty(group)) {
groupData = this.tomlData;
} else {
Object groupObj = this.tomlData.get(group);
if (groupObj instanceof Map) {
// 新建一个Map避免继承属性影响原数据
groupData = new HashMap<>((Map<String, Object>) groupObj);
} else {
throw new DbException("No config for group: [{}]", group);
}
}
// 继承属性
copyPropertyIfAbsent(groupData, DSKeys.KEY_SHOW_SQL);
copyPropertyIfAbsent(groupData, DSKeys.KEY_FORMAT_SQL);
copyPropertyIfAbsent(groupData, DSKeys.KEY_SHOW_PARAMS);
copyPropertyIfAbsent(groupData, DSKeys.KEY_SQL_LEVEL);
return toDbConfig(groupData);
}
/**
* 如果目标map中没有指定键则从全局配置复制
*/
private void copyPropertyIfAbsent(Map<String, Object> target, String key) {
if (!target.containsKey(key) && this.tomlData.containsKey(key)) {
target.put(key, this.tomlData.get(key));
}
}
/**
* 将TOML数据转换为DbConfig对象
*/
private DbConfig toDbConfig(Map<String, Object> data) {
// 基本信息
String url = getAndRemoveString(data, DSKeys.KEY_ALIAS_URL);
if (StrUtil.isBlank(url)) {
throw new DbException("No JDBC URL!");
}
// 自动识别Driver
String driver = getAndRemoveString(data, DSKeys.KEY_ALIAS_DRIVER);
if (StrUtil.isBlank(driver)) {
driver = DriverUtil.identifyDriver(url);
}
DbConfig dbConfig = DbConfig.of()
.setUrl(url)
.setDriver(driver)
.setUser(getAndRemoveString(data, DSKeys.KEY_ALIAS_USER))
.setPass(getAndRemoveString(data, DSKeys.KEY_ALIAS_PASSWORD));
// SQL日志
SqlLogFilter sqlLogFilter = getSqlLogFilter(data);
if (sqlLogFilter != null) {
dbConfig.addSqlFilter(sqlLogFilter);
}
// 大小写等配置
Boolean caseInsensitive = getAndRemoveBoolean(data, DSKeys.KEY_CASE_INSENSITIVE);
if (null != caseInsensitive) {
dbConfig.setCaseInsensitive(caseInsensitive);
}
// 连接配置
for (String key : DSKeys.KEY_CONN_PROPS) {
String connValue = getAndRemoveString(data, key);
if (StrUtil.isNotBlank(connValue)) {
dbConfig.addConnProps(key, connValue);
}
}
// 自定义连接属性
data.forEach((key, value) -> {
if(key.startsWith(CONNECTION_PREFIX) && !(value instanceof Map)){
dbConfig.addConnProps(key.substring(CONNECTION_PREFIX.length()), value.toString());
}
});
// 池属性 - 移除已处理的属性后剩余的作为池属性
data.forEach((key, value) -> {
// 非Map的属性作为池属性
if(!(value instanceof Map)){
dbConfig.addPoolProps(key, StrUtil.toStringOrNull(value));
}
});
return dbConfig;
}
/**
* 获取SQL日志过滤器
*/
private SqlLogFilter getSqlLogFilter(Map<String, Object> data) {
final Boolean isShowSql = getAndRemoveBoolean(data, DSKeys.KEY_SHOW_SQL);
if (isShowSql == null || !isShowSql) {
return null;
}
final boolean isFormatSql = ObjUtil.defaultIfNull(getAndRemoveBoolean(data, DSKeys.KEY_FORMAT_SQL), false);
final boolean isShowParams = ObjUtil.defaultIfNull(getAndRemoveBoolean(data, DSKeys.KEY_SHOW_PARAMS), false);
String sqlLevelStr = getAndRemoveString(data, DSKeys.KEY_SQL_LEVEL);
if (sqlLevelStr != null) {
sqlLevelStr = sqlLevelStr.toUpperCase();
}
Level level = ConvertUtil.toEnum(Level.class, sqlLevelStr, Level.DEBUG);
SqlLog sqlLog = new SqlLog();
sqlLog.init(true, isFormatSql, isShowParams, level);
return new SqlLogFilter(sqlLog);
}
/**
* 从map中获取字符串值
*
* @param map 源map
* @param keys 多个键
* @return 字符串值
*/
private String getAndRemoveString(Map<String, Object> map, String... keys) {
Object value = null;
for (String key : keys) {
value = map.remove(key);
if (null != value) {
break;
}
}
return StrUtil.toStringOrNull(value);
}
/**
* 从map中获取布尔值
*
* @param map 源map
* @param keys 多个键
* @return 布尔值
*/
private Boolean getAndRemoveBoolean(Map<String, Object> map, String... keys) {
Object value = null;
for (String key : keys) {
value = map.remove(key);
if (null != value) {
break;
}
}
return ConvertUtil.toBoolean(value);
}
}

View File

@ -0,0 +1,37 @@
package cn.hutool.v7.db.config;
import cn.hutool.v7.core.lang.Console;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class TomlConfigParserTest {
@Test
public void parseTest() {
DbConfig dbConfig = TomlConfigParser.of().parse("");
assertEquals("org.sqlite.JDBC", dbConfig.getDriver());
assertEquals("jdbc:sqlite:test.db", dbConfig.getUrl());
assertNull(dbConfig.getUser());
assertNull(dbConfig.getPass());
assertEquals(1, dbConfig.getConnProps().size());
assertEquals("true", dbConfig.getConnProps().getProperty("remarks"));
assertNull(dbConfig.getPoolProps());
}
@Test
void parseOrclTest(){
DbConfig dbConfig = TomlConfigParser.of().parse("orcl");
Console.log(dbConfig);
assertEquals("oracle.jdbc.OracleDriver", dbConfig.getDriver());
assertEquals("jdbc:oracle:thin:@//localhost:1521/XEPDB1", dbConfig.getUrl());
assertEquals("system", dbConfig.getUser());
assertEquals("123456", dbConfig.getPass());
assertEquals(1, dbConfig.getConnProps().size());
assertEquals("true", dbConfig.getConnProps().getProperty("remarks"));
assertNull(dbConfig.getPoolProps());
}
}

View File

@ -0,0 +1,115 @@
#
# Copyright (c) 2013-2025 Hutool Team and hutool.cn
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# suppress inspection "Annotator" for whole file
#===================================================================
# 数据库配置文件样例
# DsFactory默认读取的配置文件是config/db.setting
# db.setting的配置包括两部分基本连接信息和连接池配置信息。
# 基本连接信息所有连接池都支持,连接池配置信息根据不同的连接池,连接池配置是根据连接池相应的配置项移植而来
#===================================================================
## 打印SQL的配置
# 是否在日志中显示执行的SQL默认false
showSql = true
# 是否格式化显示的SQL默认false
formatSql = true
# 是否显示SQL参数默认false
showParams = true
# 打印SQL的日志等级默认debug
sqlLevel = "debug"
# 默认数据源
url = "jdbc:sqlite:test.db"
remarks = true
# 测试数据源
[test]
url = "jdbc:sqlite:test.db"
remarks = true
driver = "org.sqlite.JDBC"
# 测试用HSQLDB数据库
[hsqldb]
url = "jdbc:hsqldb:mem:mem_hutool"
user = "SA"
pass = ""
remarks = true
# 测试用HSQLDB数据库
[h2]
url = "jdbc:h2:mem:h2_hutool"
user = "sa"
pass = ""
remarks = true
# 测试用HSQLDB数据库
[derby]
url = "jdbc:derby:.derby/test_db;create=true"
remarks = true
# 测试用Oracle数据库
[orcl]
url = "jdbc:oracle:thin:@//localhost:1521/XEPDB1"
user = "system"
pass = "123456"
remarks = true
[mysql]
url = "jdbc:mysql://Looly.centos8:3306/hutool_test?useSSL=false"
user = "root"
pass = "123456"
remarks = true
[mariadb_local]
url = "jdbc:mysql://localhost:3306/test?useSSL=false"
user = "root"
pass = "123456"
remarks = true
[postgre]
url = "jdbc:postgresql://localhost:5432/test_hutool"
user = "postgres"
pass = "123456"
remarks = true
[sqlserver]
url = "jdbc:sqlserver://Looly.database.chinacloudapi.cn:1433;database=test;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.chinacloudapi.cn;loginTimeout=30;"
user = "Looly@Looly"
pass = "123"
remarks = true
# 测试用达梦8数据库
# 测试环境使用docker启动https://eco.dameng.com/document/dm/zh-cn/start/dm-install-docker.html
[dm]
url = "jdbc:dm://localhost:5236"
user = "SYSDBA"
pass = "SYSDBA001"
remarks = true
# OceanBase
# 测试环境使用docker启动https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000217958
[ob]
url = "jdbc:oceanbase://localhost:2881/test"
user = "root"
pass = "123456"
remarks = true
[hana]
url = "jdbc:sap://localhost:30015/HAP_CONN?autoReconnect=true"
user = "DB"
pass = "123456"
remarks = "true"