fix: 优化了动态数据源配置
This commit is contained in:
parent
d4992b9687
commit
f0844de9ec
@ -2,21 +2,31 @@ package com.yfd.platform.datasource;
|
||||
|
||||
/**
|
||||
* 数据源 key 常量
|
||||
* <p>
|
||||
* 系统内置 key(yml 固定配置):
|
||||
* <ul>
|
||||
* <li>{@link #DM_MASTER} / {@link #DM_SLAVE} — 达梦数据库</li>
|
||||
* </ul>
|
||||
* 扩展数据源 key 格式:{@code ext-{type}-{序号}},如 {@code ext-oracle-01}、{@code ext-mysql-01},
|
||||
* 由数据库表 {@code GEN_DATASOURCE_CONF.DS_KEY} 配置,运行时动态注册。
|
||||
*/
|
||||
public interface DataSourceKeys {
|
||||
|
||||
/** 达梦主库 */
|
||||
/** 达梦主库(业务主库,yml 固定配置) */
|
||||
String DM_MASTER = "dm-master";
|
||||
|
||||
/** 达梦从库 */
|
||||
String DM_SLAVE = "dm-slave";
|
||||
|
||||
/** Oracle 主库 */
|
||||
/** Oracle 主库(由数据库表 GEN_DATASOURCE_CONF 动态注册,不再从 yml 配置) */
|
||||
String ORACLE_MASTER = "oracle-master";
|
||||
|
||||
/** Oracle 从库 */
|
||||
/** Oracle 从库(由数据库表 GEN_DATASOURCE_CONF 动态注册,不再从 yml 配置) */
|
||||
String ORACLE_SLAVE = "oracle-slave";
|
||||
|
||||
/** 默认数据源 */
|
||||
String DEFAULT = DM_MASTER;
|
||||
|
||||
/** 扩展数据源 key 前缀 */
|
||||
String EXT_PREFIX = "ext-";
|
||||
}
|
||||
@ -1,23 +1,39 @@
|
||||
package com.yfd.platform.datasource;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/******************************
|
||||
* 用途说明:
|
||||
* 作者姓名: wxy
|
||||
* 创建时间: 2022/9/23 17:47
|
||||
******************************/
|
||||
/**
|
||||
* 动态数据源路由,支持运行时注册/注销/热刷新
|
||||
*
|
||||
* @author wxy
|
||||
*/
|
||||
public class DynamicDataSource extends AbstractRoutingDataSource {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);
|
||||
|
||||
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
|
||||
|
||||
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, DataSource> targetDataSources) {
|
||||
/**
|
||||
* 用户手动注册的目标数据源(含 yml 配置 + 数据库表注册)
|
||||
*/
|
||||
private final Map<Object, Object> targetDataSources = new HashMap<>();
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, DataSource> initialDataSources) {
|
||||
super.setDefaultTargetDataSource(defaultTargetDataSource);
|
||||
super.setTargetDataSources(new HashMap<Object, Object>(targetDataSources));
|
||||
super.afterPropertiesSet();
|
||||
if (initialDataSources != null) {
|
||||
this.targetDataSources.putAll(initialDataSources);
|
||||
}
|
||||
rebuildResolvedDataSources();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -38,10 +54,96 @@ public class DynamicDataSource extends AbstractRoutingDataSource {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据数据源 key 获取目标数据源实例(如 dm-master / oracle-master)
|
||||
* 根据数据源 key 获取目标数据源实例
|
||||
*/
|
||||
public DataSource getDataSourceByKey(String key) {
|
||||
Map<Object, DataSource> resolved = getResolvedDataSources();
|
||||
return resolved == null ? null : resolved.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时注册一个数据源,线程安全,立即生效
|
||||
*
|
||||
* @param key 路由 key,如 "ext-oracle-01"
|
||||
* @param ds 数据源实例
|
||||
*/
|
||||
public void register(String key, DataSource ds) {
|
||||
synchronized (lock) {
|
||||
// 如果已有旧数据源且不同实例,关闭旧连接池
|
||||
Object old = targetDataSources.get(key);
|
||||
if (old instanceof DruidDataSource && old != ds) {
|
||||
closeQuietly((DruidDataSource) old);
|
||||
}
|
||||
targetDataSources.put(key, ds);
|
||||
rebuildResolvedDataSources();
|
||||
log.info("数据源已注册: key={}, type={}", key, ds.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时注销一个数据源,线程安全,立即生效
|
||||
*
|
||||
* @param key 路由 key
|
||||
* @return true-已注销并关闭连接池;false-未找到该 key
|
||||
*/
|
||||
public boolean unregister(String key) {
|
||||
synchronized (lock) {
|
||||
Object removed = targetDataSources.remove(key);
|
||||
if (removed == null) {
|
||||
return false;
|
||||
}
|
||||
rebuildResolvedDataSources();
|
||||
closeQuietly(removed);
|
||||
log.info("数据源已注销: key={}", key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量替换同名数据源(先保留旧连接池,注册新实例,再关闭旧的)
|
||||
* 用于热刷新时零中断更新
|
||||
*/
|
||||
public void registerReplace(String key, DataSource newDs) {
|
||||
synchronized (lock) {
|
||||
Object old = targetDataSources.get(key);
|
||||
targetDataSources.put(key, newDs);
|
||||
rebuildResolvedDataSources();
|
||||
closeQuietly(old);
|
||||
log.info("数据源已热替换: key={}", key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已注册的数据源 key
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Set<Object> getRegisteredKeys() {
|
||||
Map<Object, DataSource> resolved = getResolvedDataSources();
|
||||
return resolved == null ? Set.of() : resolved.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 key 是否已注册
|
||||
*/
|
||||
public boolean containsKey(String key) {
|
||||
return getDataSourceByKey(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建路由表(内部同步需由调用方保证线程安全)
|
||||
*/
|
||||
private void rebuildResolvedDataSources() {
|
||||
super.setTargetDataSources(new HashMap<>(targetDataSources));
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
|
||||
private void closeQuietly(Object ds) {
|
||||
if (ds instanceof DruidDataSource) {
|
||||
try {
|
||||
((DruidDataSource) ds).close();
|
||||
} catch (Exception e) {
|
||||
log.warn("关闭旧数据源连接池异常: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,17 +14,17 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态数据源配置,支持 达梦(DM) 和 Oracle 两种数据库,每种各有主从(master/slave)。
|
||||
* 动态数据源配置,仅保留业务主库(达梦 DM)的 yml 配置。
|
||||
* <p>
|
||||
* 每个数据源可通过配置项 {@code spring.datasource.druid.<key>.enabled} 按需开启/关闭,
|
||||
* 关闭后不会创建对应的连接池,也不会注册到路由数据源中。
|
||||
* 其他数据源(如 Oracle、MySQL、PostgreSQL 等)通过数据库表
|
||||
* {@code GEN_DATASOURCE_CONF} 动态注册,见 {@link DynamicDataSourceRegistry}。
|
||||
*
|
||||
* @author yfd
|
||||
*/
|
||||
@Configuration
|
||||
public class DynamicDataSourceConfig {
|
||||
|
||||
// ==================== 达梦(DM) 数据源 ====================
|
||||
// ==================== 达梦(DM) 数据源(业务主库,yml 固定配置) ====================
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("spring.datasource.druid.dm-master")
|
||||
@ -40,37 +40,17 @@ public class DynamicDataSourceConfig {
|
||||
return newDruidDataSource();
|
||||
}
|
||||
|
||||
// ==================== Oracle 数据源 ====================
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("spring.datasource.druid.oracle-master")
|
||||
@ConditionalOnProperty(prefix = "spring.datasource.druid.oracle-master", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public DataSource oracleMasterDataSource() {
|
||||
return newDruidDataSource();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("spring.datasource.druid.oracle-slave")
|
||||
@ConditionalOnProperty(prefix = "spring.datasource.druid.oracle-slave", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public DataSource oracleSlaveDataSource() {
|
||||
return newDruidDataSource();
|
||||
}
|
||||
|
||||
// ==================== 路由数据源 ====================
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public DynamicDataSource dataSource(@Qualifier("dmMasterDataSource") ObjectProvider<DataSource> dmMaster,
|
||||
@Qualifier("dmSlaveDataSource") ObjectProvider<DataSource> dmSlave,
|
||||
@Qualifier("oracleMasterDataSource") ObjectProvider<DataSource> oracleMaster,
|
||||
@Qualifier("oracleSlaveDataSource") ObjectProvider<DataSource> oracleSlave) {
|
||||
@Qualifier("dmSlaveDataSource") ObjectProvider<DataSource> dmSlave) {
|
||||
Map<Object, DataSource> targetDataSources = new HashMap<>();
|
||||
putIfPresent(targetDataSources, DataSourceKeys.DM_MASTER, dmMaster);
|
||||
putIfPresent(targetDataSources, DataSourceKeys.DM_SLAVE, dmSlave);
|
||||
putIfPresent(targetDataSources, DataSourceKeys.ORACLE_MASTER, oracleMaster);
|
||||
putIfPresent(targetDataSources, DataSourceKeys.ORACLE_SLAVE, oracleSlave);
|
||||
|
||||
// 默认优先使用达梦主库;若未开启则回退到任意一个已开启的数据源
|
||||
// 默认使用达梦主库
|
||||
DataSource defaultDataSource = dmMaster.getIfAvailable();
|
||||
if (defaultDataSource == null) {
|
||||
defaultDataSource = targetDataSources.values().stream().findFirst()
|
||||
|
||||
@ -0,0 +1,106 @@
|
||||
package com.yfd.platform.datasource;
|
||||
|
||||
import com.yfd.platform.config.ResponseResult;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 动态数据源管理控制器,提供运行时数据源注册/注销/热刷新/列表查询等操作。
|
||||
* <p>
|
||||
* 数据源配置存储在数据库表 {@code GEN_DATASOURCE_CONF} 中,通过此接口可:
|
||||
* <ul>
|
||||
* <li>查看当前已注册的所有数据源</li>
|
||||
* <li>手动注册/注销单个数据源</li>
|
||||
* <li>从数据库表热刷新全部数据源</li>
|
||||
* </ul>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/datasource")
|
||||
@Tag(name = "动态数据源管理")
|
||||
@Validated
|
||||
public class DynamicDataSourceController {
|
||||
|
||||
@Resource
|
||||
private DynamicDataSourceRegistry dynamicDataSourceRegistry;
|
||||
|
||||
@Resource
|
||||
private DynamicDataSource dynamicDataSource;
|
||||
|
||||
/**
|
||||
* 列出所有已注册的数据源(含系统内置 dm-master/dm-slave)
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "列出所有已注册的数据源")
|
||||
public ResponseResult listDataSources() {
|
||||
Set<Object> keys = dynamicDataSource.getRegisteredKeys();
|
||||
List<Map<String, Object>> list = new ArrayList<>(keys.size());
|
||||
for (Object key : keys) {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("key", key);
|
||||
DataSource ds = dynamicDataSource.getDataSourceByKey(key.toString());
|
||||
item.put("type", ds == null ? "unknown" : ds.getClass().getSimpleName());
|
||||
item.put("alive", ds != null);
|
||||
list.add(item);
|
||||
}
|
||||
// 内置 key 排前,扩展 key 排后
|
||||
list.sort(Comparator.comparing(m -> {
|
||||
String k = (String) m.get("key");
|
||||
if (DataSourceKeys.DM_MASTER.equals(k)) return 0;
|
||||
if (DataSourceKeys.DM_SLAVE.equals(k)) return 1;
|
||||
return 2;
|
||||
}));
|
||||
return ResponseResult.successData(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 热刷新所有数据源:从数据库表重新读取配置并注册
|
||||
*/
|
||||
@PostMapping("/refresh")
|
||||
@Operation(summary = "热刷新数据源(从数据库表重新读取并注册)")
|
||||
public ResponseResult refreshDataSources() {
|
||||
dynamicDataSourceRegistry.refresh();
|
||||
return ResponseResult.success("动态数据源热刷新完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动注册一个数据源(仅注册到路由,不持久化到数据库表)
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
@Operation(summary = "手动注册一个数据源(仅运行时,不持久化到表)")
|
||||
public ResponseResult registerDataSource(@RequestBody GenDatasourceConf conf) {
|
||||
if (conf.getDsKey() == null || conf.getDsKey().isBlank()) {
|
||||
return ResponseResult.error("dsKey 不能为空");
|
||||
}
|
||||
if (conf.getUrl() == null || conf.getUrl().isBlank()) {
|
||||
return ResponseResult.error("url 不能为空");
|
||||
}
|
||||
if (dynamicDataSourceRegistry.registerRuntime(conf)) {
|
||||
return ResponseResult.success("数据源手动注册成功: " + conf.getDsKey());
|
||||
} else {
|
||||
return ResponseResult.error("数据源注册失败: " + conf.getDsKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销一个数据源
|
||||
*/
|
||||
@DeleteMapping("/unregister/{key}")
|
||||
@Operation(summary = "注销一个数据源")
|
||||
public ResponseResult unregisterDataSource(@PathVariable("key") String key) {
|
||||
if (DataSourceKeys.DM_MASTER.equals(key) || DataSourceKeys.DM_SLAVE.equals(key)) {
|
||||
return ResponseResult.error("系统内置数据源不允许注销");
|
||||
}
|
||||
boolean removed = dynamicDataSource.unregister(key);
|
||||
if (removed) {
|
||||
return ResponseResult.success("数据源已注销: " + key);
|
||||
} else {
|
||||
return ResponseResult.error("未找到数据源: " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,300 @@
|
||||
package com.yfd.platform.datasource;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态数据源注册器,从数据库表 {@code GEN_DATASOURCE_CONF} 读取配置,
|
||||
* 构建 Druid 连接池并注册到 {@link DynamicDataSource},支持运行时热刷新。
|
||||
* <p>
|
||||
* 使用方式:
|
||||
* <ul>
|
||||
* <li>启动时 {@link #init()} 自动注册所有已启用的数据源</li>
|
||||
* <li>运行时调用 {@link #refresh()} 重新读取表并热刷新(零中断)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Component
|
||||
public class DynamicDataSourceRegistry {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceRegistry.class);
|
||||
|
||||
/** 业务主库(dm-master)的 DataSource,用于查询 GEN_DATASOURCE_CONF 表 */
|
||||
@Resource
|
||||
@Qualifier("dmMasterDataSource")
|
||||
private DataSource dmMasterDataSource;
|
||||
|
||||
@Resource
|
||||
private DynamicDataSource dynamicDataSource;
|
||||
|
||||
private final Object refreshLock = new Object();
|
||||
|
||||
/**
|
||||
* 启动时初始化:从数据库表加载所有已启用的数据源并注册
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
try {
|
||||
List<GenDatasourceConf> configs = loadConfigs();
|
||||
if (configs.isEmpty()) {
|
||||
log.info("GEN_DATASOURCE_CONF 表中无已启用的动态数据源配置,跳过注册");
|
||||
return;
|
||||
}
|
||||
int success = 0;
|
||||
int fail = 0;
|
||||
for (GenDatasourceConf conf : configs) {
|
||||
if (tryRegister(conf)) {
|
||||
success++;
|
||||
} else {
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
log.info("动态数据源初始化完成: 成功={}, 失败={}", success, fail);
|
||||
} catch (Exception e) {
|
||||
log.error("动态数据源初始化失败(业务主库未就绪,稍后手动刷新即可): {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时热刷新:重新读取数据库表,自动注册新增/变更的数据源,注销已删除/禁用的数据源
|
||||
*/
|
||||
public void refresh() {
|
||||
synchronized (refreshLock) {
|
||||
log.info("开始热刷新动态数据源...");
|
||||
List<GenDatasourceConf> configs = loadConfigs();
|
||||
|
||||
// 1. 收集表中期望的 key
|
||||
java.util.Set<String> expectedKeys = new java.util.HashSet<>();
|
||||
for (GenDatasourceConf conf : configs) {
|
||||
if (conf.getDsKey() != null) {
|
||||
expectedKeys.add(conf.getDsKey());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 注册或替换表中数据源
|
||||
int success = 0;
|
||||
int fail = 0;
|
||||
for (GenDatasourceConf conf : configs) {
|
||||
if (tryRegister(conf)) {
|
||||
success++;
|
||||
} else {
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 注销表中已不存在或已禁用的数据源(排除内置的 dm-master/dm-slave)
|
||||
java.util.Set<Object> registeredKeys = dynamicDataSource.getRegisteredKeys();
|
||||
for (Object keyObj : registeredKeys) {
|
||||
String key = keyObj.toString();
|
||||
// 跳过系统内置数据源
|
||||
if (DataSourceKeys.DM_MASTER.equals(key) || DataSourceKeys.DM_SLAVE.equals(key)) {
|
||||
continue;
|
||||
}
|
||||
if (!expectedKeys.contains(key)) {
|
||||
dynamicDataSource.unregister(key);
|
||||
log.info("热刷新: 已注销数据源 key={}", key);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("动态数据源热刷新完成: 成功={}, 失败={}, 注销={}", success, fail,
|
||||
registeredKeys.size() - expectedKeys.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库表查询所有已启用的数据源配置
|
||||
*/
|
||||
private List<GenDatasourceConf> loadConfigs() {
|
||||
JdbcTemplate jt = new JdbcTemplate(dmMasterDataSource);
|
||||
String sql = "SELECT * FROM GEN_DATASOURCE_CONF WHERE NVL(IS_DELETED, 0) = 0 AND NVL(ENABLED, 1) = 1 ORDER BY ORDER_INDEX";
|
||||
List<Map<String, Object>> rows = jt.queryForList(sql);
|
||||
List<GenDatasourceConf> list = new ArrayList<>(rows.size());
|
||||
for (Map<String, Object> row : rows) {
|
||||
GenDatasourceConf conf = mapRow(row);
|
||||
if (conf.getDsKey() == null || conf.getDsKey().isBlank()) {
|
||||
log.warn("GEN_DATASOURCE_CONF 记录 ID={} 缺少 DS_KEY,跳过", row.get("ID"));
|
||||
continue;
|
||||
}
|
||||
list.add(conf);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试注册单个数据源
|
||||
*
|
||||
* @return true-注册成功;false-注册失败
|
||||
*/
|
||||
private boolean tryRegister(GenDatasourceConf conf) {
|
||||
String key = conf.getDsKey().trim();
|
||||
try {
|
||||
DruidDataSource ds = buildDataSource(conf);
|
||||
// 使用 registerReplace 热替换,运行时零中断
|
||||
dynamicDataSource.registerReplace(key, ds);
|
||||
log.info("数据源注册成功: key={}, url={}", key, conf.getUrl());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("数据源注册失败: key={}, url={}, 原因={}", key, conf.getUrl(), e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时手动注册(API 调用),不持久化到数据库表
|
||||
*
|
||||
* @return true-注册成功
|
||||
*/
|
||||
public boolean registerRuntime(GenDatasourceConf conf) {
|
||||
return tryRegister(conf);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置构建 Druid 数据源
|
||||
*/
|
||||
private DruidDataSource buildDataSource(GenDatasourceConf conf) {
|
||||
DruidDataSource ds = new DruidDataSource();
|
||||
|
||||
// 驱动:先按 URL 自动识别,再按配置
|
||||
String driver = conf.getDriver();
|
||||
if (driver == null || driver.isBlank()) {
|
||||
driver = resolveDriverFromUrl(conf.getUrl());
|
||||
}
|
||||
ds.setDriverClassName(driver);
|
||||
ds.setUrl(conf.getUrl());
|
||||
ds.setUsername(conf.getUsername());
|
||||
ds.setPassword(conf.getPassword());
|
||||
|
||||
// 连接池大小
|
||||
int maxActive = (conf.getMaxActive() != null && conf.getMaxActive() > 0)
|
||||
? conf.getMaxActive() : 20;
|
||||
ds.setInitialSize(Math.min(5, maxActive));
|
||||
ds.setMinIdle(Math.min(5, maxActive));
|
||||
ds.setMaxActive(maxActive);
|
||||
|
||||
// 超时
|
||||
long connTimeout = (conf.getConnTimeout() != null && conf.getConnTimeout() > 0)
|
||||
? conf.getConnTimeout() : 30000L;
|
||||
ds.setMaxWait(connTimeout);
|
||||
|
||||
// 通用连接池配置
|
||||
ds.setValidationQuery("SELECT 1");
|
||||
ds.setValidationQueryTimeout(3);
|
||||
ds.setTestWhileIdle(true);
|
||||
ds.setTestOnBorrow(false);
|
||||
ds.setTestOnReturn(false);
|
||||
ds.setKeepAlive(true);
|
||||
ds.setBreakAfterAcquireFailure(true);
|
||||
ds.setConnectionErrorRetryAttempts(0);
|
||||
ds.setTimeBetweenConnectErrorMillis(30000);
|
||||
ds.setTimeBetweenEvictionRunsMillis(60000);
|
||||
ds.setMinEvictableIdleTimeMillis(180000);
|
||||
ds.setMaxEvictableIdleTimeMillis(300000);
|
||||
ds.setRemoveAbandoned(true);
|
||||
ds.setRemoveAbandonedTimeout(1800);
|
||||
ds.setLogAbandoned(true);
|
||||
|
||||
try {
|
||||
ds.init();
|
||||
} catch (SQLException e) {
|
||||
// 关闭半初始化的连接池再抛出
|
||||
ds.close();
|
||||
throw new IllegalStateException("初始化数据源失败: " + conf.getUrl(), e);
|
||||
}
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 URL 自动识别 JDBC 驱动类名
|
||||
*/
|
||||
private String resolveDriverFromUrl(String url) {
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("URL 为空,无法自动识别驱动类型");
|
||||
}
|
||||
String lower = url.toLowerCase();
|
||||
if (lower.startsWith("jdbc:dm")) {
|
||||
return "dm.jdbc.driver.DmDriver";
|
||||
}
|
||||
if (lower.startsWith("jdbc:oracle")) {
|
||||
return "oracle.jdbc.OracleDriver";
|
||||
}
|
||||
if (lower.startsWith("jdbc:mysql")) {
|
||||
return "com.mysql.cj.jdbc.Driver";
|
||||
}
|
||||
if (lower.startsWith("jdbc:postgresql")) {
|
||||
return "org.postgresql.Driver";
|
||||
}
|
||||
if (lower.startsWith("jdbc:sqlserver") || lower.startsWith("jdbc:microsoft")) {
|
||||
return "com.microsoft.sqlserver.jdbc.SQLServerDriver";
|
||||
}
|
||||
throw new IllegalArgumentException("无法根据 URL 自动识别驱动类型: " + url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据库行映射为实体
|
||||
*/
|
||||
private GenDatasourceConf mapRow(Map<String, Object> row) {
|
||||
GenDatasourceConf conf = new GenDatasourceConf();
|
||||
conf.setId(strVal(row.get("ID")));
|
||||
conf.setDriver(strVal(row.get("DRIVER")));
|
||||
conf.setUrl(strVal(row.get("URL")));
|
||||
conf.setUsername(strVal(row.get("USERNAME")));
|
||||
conf.setPassword(strVal(row.get("PASSWORD")));
|
||||
conf.setInternal(intVal(row.get("INTERNAL")));
|
||||
conf.setOrderIndex(intVal(row.get("ORDER_INDEX")));
|
||||
conf.setFilterContent(strVal(row.get("FILTER_CONTENT")));
|
||||
conf.setRecordUser(strVal(row.get("RECORD_USER")));
|
||||
conf.setRecordTime(dateVal(row.get("RECORD_TIME")));
|
||||
conf.setModifyUser(strVal(row.get("MODIFY_USER")));
|
||||
conf.setModifyTime(dateVal(row.get("MODIFY_TIME")));
|
||||
conf.setIsDeleted(intVal(row.get("IS_DELETED")));
|
||||
conf.setDeleteUser(strVal(row.get("DELETE_USER")));
|
||||
conf.setDeleteTime(dateVal(row.get("DELETE_TIME")));
|
||||
conf.setCreateDate(dateVal(row.get("CREATE_DATE")));
|
||||
conf.setUpdateDate(dateVal(row.get("UPDATE_DATE")));
|
||||
conf.setDelFlag(strVal(row.get("DEL_FLAG")));
|
||||
// 扩展字段
|
||||
conf.setDsKey(strVal(row.get("DS_KEY")));
|
||||
conf.setEnabled(intVal(row.get("ENABLED")));
|
||||
conf.setDsName(strVal(row.get("DS_NAME")));
|
||||
conf.setDsDesc(strVal(row.get("DS_DESC")));
|
||||
conf.setConnTimeout(longVal(row.get("CONN_TIMEOUT")));
|
||||
conf.setMaxActive(intVal(row.get("MAX_ACTIVE")));
|
||||
return conf;
|
||||
}
|
||||
|
||||
private String strVal(Object obj) {
|
||||
return obj == null ? null : obj.toString();
|
||||
}
|
||||
|
||||
private Integer intVal(Object obj) {
|
||||
if (obj == null) return null;
|
||||
if (obj instanceof Number) return ((Number) obj).intValue();
|
||||
try { return Integer.parseInt(obj.toString()); } catch (Exception e) { return null; }
|
||||
}
|
||||
|
||||
private Long longVal(Object obj) {
|
||||
if (obj == null) return null;
|
||||
if (obj instanceof Number) return ((Number) obj).longValue();
|
||||
try { return Long.parseLong(obj.toString()); } catch (Exception e) { return null; }
|
||||
}
|
||||
|
||||
private java.util.Date dateVal(Object obj) {
|
||||
if (obj == null) return null;
|
||||
if (obj instanceof java.util.Date) return (java.util.Date) obj;
|
||||
if (obj instanceof java.sql.Date) return new java.util.Date(((java.sql.Date) obj).getTime());
|
||||
if (obj instanceof java.sql.Timestamp) return new java.util.Date(((java.sql.Timestamp) obj).getTime());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
package com.yfd.platform.datasource;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* GEN_DATASOURCE_CONF 数据源配置表实体,用于动态数据源注册
|
||||
* <p>
|
||||
* 字段与数据库表一一对应,使用 {@link com.yfd.platform.common.MicroservicDynamicSQLMapper#getAllList}
|
||||
* 动态 SQL 查询 / 手动映射,不依赖 MyBatis-Plus 实体扫描。
|
||||
*/
|
||||
public class GenDatasourceConf {
|
||||
|
||||
private String id;
|
||||
private String driver;
|
||||
private String url;
|
||||
private String username;
|
||||
private String password;
|
||||
private Integer internal;
|
||||
private Integer orderIndex;
|
||||
private String filterContent;
|
||||
private String recordUser;
|
||||
private Date recordTime;
|
||||
private String modifyUser;
|
||||
private Date modifyTime;
|
||||
private Integer isDeleted;
|
||||
private String deleteUser;
|
||||
private Date deleteTime;
|
||||
private Date createDate;
|
||||
private Date updateDate;
|
||||
private String delFlag;
|
||||
|
||||
// ========== 扩展字段 ==========
|
||||
|
||||
/** 数据源路由 key,全局唯一 */
|
||||
private String dsKey;
|
||||
|
||||
/** 是否启用:0=禁用 1=启用 */
|
||||
private Integer enabled;
|
||||
|
||||
/** 数据源显示名称 */
|
||||
private String dsName;
|
||||
|
||||
/** 数据源描述 */
|
||||
private String dsDesc;
|
||||
|
||||
/** 连接超时(毫秒),默认30000 */
|
||||
private Long connTimeout;
|
||||
|
||||
/** 最大活跃连接数,默认20 */
|
||||
private Integer maxActive;
|
||||
|
||||
// ==================== Getters & Setters ====================
|
||||
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getDriver() { return driver; }
|
||||
public void setDriver(String driver) { this.driver = driver; }
|
||||
|
||||
public String getUrl() { return url; }
|
||||
public void setUrl(String url) { this.url = url; }
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
|
||||
public Integer getInternal() { return internal; }
|
||||
public void setInternal(Integer internal) { this.internal = internal; }
|
||||
|
||||
public Integer getOrderIndex() { return orderIndex; }
|
||||
public void setOrderIndex(Integer orderIndex) { this.orderIndex = orderIndex; }
|
||||
|
||||
public String getFilterContent() { return filterContent; }
|
||||
public void setFilterContent(String filterContent) { this.filterContent = filterContent; }
|
||||
|
||||
public String getRecordUser() { return recordUser; }
|
||||
public void setRecordUser(String recordUser) { this.recordUser = recordUser; }
|
||||
|
||||
public Date getRecordTime() { return recordTime; }
|
||||
public void setRecordTime(Date recordTime) { this.recordTime = recordTime; }
|
||||
|
||||
public String getModifyUser() { return modifyUser; }
|
||||
public void setModifyUser(String modifyUser) { this.modifyUser = modifyUser; }
|
||||
|
||||
public Date getModifyTime() { return modifyTime; }
|
||||
public void setModifyTime(Date modifyTime) { this.modifyTime = modifyTime; }
|
||||
|
||||
public Integer getIsDeleted() { return isDeleted; }
|
||||
public void setIsDeleted(Integer isDeleted) { this.isDeleted = isDeleted; }
|
||||
|
||||
public String getDeleteUser() { return deleteUser; }
|
||||
public void setDeleteUser(String deleteUser) { this.deleteUser = deleteUser; }
|
||||
|
||||
public Date getDeleteTime() { return deleteTime; }
|
||||
public void setDeleteTime(Date deleteTime) { this.deleteTime = deleteTime; }
|
||||
|
||||
public Date getCreateDate() { return createDate; }
|
||||
public void setCreateDate(Date createDate) { this.createDate = createDate; }
|
||||
|
||||
public Date getUpdateDate() { return updateDate; }
|
||||
public void setUpdateDate(Date updateDate) { this.updateDate = updateDate; }
|
||||
|
||||
public String getDelFlag() { return delFlag; }
|
||||
public void setDelFlag(String delFlag) { this.delFlag = delFlag; }
|
||||
|
||||
public String getDsKey() { return dsKey; }
|
||||
public void setDsKey(String dsKey) { this.dsKey = dsKey; }
|
||||
|
||||
public Integer getEnabled() { return enabled; }
|
||||
public void setEnabled(Integer enabled) { this.enabled = enabled; }
|
||||
|
||||
public String getDsName() { return dsName; }
|
||||
public void setDsName(String dsName) { this.dsName = dsName; }
|
||||
|
||||
public String getDsDesc() { return dsDesc; }
|
||||
public void setDsDesc(String dsDesc) { this.dsDesc = dsDesc; }
|
||||
|
||||
public Long getConnTimeout() { return connTimeout; }
|
||||
public void setConnTimeout(Long connTimeout) { this.connTimeout = connTimeout; }
|
||||
|
||||
public Integer getMaxActive() { return maxActive; }
|
||||
public void setMaxActive(Integer maxActive) { this.maxActive = maxActive; }
|
||||
}
|
||||
@ -69,70 +69,6 @@ spring:
|
||||
pool-prepared-statements: true
|
||||
max-open-prepared-statements: 100
|
||||
max-pool-prepared-statement-per-connection-size: 100
|
||||
# ==================== Oracle 数据源 ====================
|
||||
# enabled=false 时不创建连接池,也不注册到路由数据源(按需开启)
|
||||
oracle-master:
|
||||
enabled: false
|
||||
driverClassName: oracle.jdbc.OracleDriver
|
||||
url: "${DB_ORACLE_MASTER_URL:jdbc:oracle:thin:@172.16.31.172:1521/SDLYZ}"
|
||||
username: "${DB_ORACLE_MASTER_USERNAME:SDLY_QX}"
|
||||
password: "${DB_ORACLE_MASTER_PASSWORD:jNHnqv3hH7}"
|
||||
initial-size: 5
|
||||
min-idle: 5
|
||||
max-active: 20
|
||||
max-wait: 30000
|
||||
async-init: true
|
||||
keep-alive-between-time-millis: 120000
|
||||
time-between-eviction-runs-millis: 60000
|
||||
min-evictable-idle-time-millis: 180000
|
||||
max-evictable-idle-time-millis: 300000
|
||||
phy-timeout-millis: 25200000
|
||||
validation-query: SELECT 1 FROM DUAL
|
||||
validation-query-timeout: 3
|
||||
test-while-idle: true
|
||||
test-on-borrow: false
|
||||
test-on-return: false
|
||||
keep-alive: true
|
||||
remove-abandoned: true
|
||||
remove-abandoned-timeout: 1800
|
||||
log-abandoned: true
|
||||
break-after-acquire-failure: true
|
||||
time-between-connect-error-millis: 30000
|
||||
pool-prepared-statements: true
|
||||
max-open-prepared-statements: 100
|
||||
max-pool-prepared-statement-per-connection-size: 100
|
||||
connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
|
||||
oracle-slave:
|
||||
enabled: false
|
||||
driverClassName: oracle.jdbc.OracleDriver
|
||||
url: "${DB_ORACLE_SLAVE_URL:jdbc:oracle:thin:@172.16.31.172:1521/SDLYZ}"
|
||||
username: "${DB_ORACLE_SLAVE_USERNAME:SDLY_QX}"
|
||||
password: "${DB_ORACLE_SLAVE_PASSWORD:jNHnqv3hH7}"
|
||||
initial-size: 5
|
||||
min-idle: 5
|
||||
max-active: 20
|
||||
max-wait: 30000
|
||||
async-init: true
|
||||
keep-alive-between-time-millis: 120000
|
||||
time-between-eviction-runs-millis: 60000
|
||||
min-evictable-idle-time-millis: 180000
|
||||
max-evictable-idle-time-millis: 300000
|
||||
phy-timeout-millis: 25200000
|
||||
validation-query: SELECT 1 FROM DUAL
|
||||
validation-query-timeout: 3
|
||||
test-while-idle: true
|
||||
test-on-borrow: false
|
||||
test-on-return: false
|
||||
keep-alive: true
|
||||
remove-abandoned: true
|
||||
remove-abandoned-timeout: 1800
|
||||
log-abandoned: true
|
||||
break-after-acquire-failure: true
|
||||
time-between-connect-error-millis: 30000
|
||||
pool-prepared-statements: true
|
||||
max-open-prepared-statements: 100
|
||||
max-pool-prepared-statement-per-connection-size: 100
|
||||
connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
|
||||
filter:
|
||||
stat:
|
||||
enabled: true
|
||||
|
||||
@ -69,70 +69,6 @@ spring:
|
||||
pool-prepared-statements: true
|
||||
max-open-prepared-statements: 100
|
||||
max-pool-prepared-statement-per-connection-size: 100
|
||||
# ==================== Oracle 数据源 ====================
|
||||
# enabled=false 时不创建连接池,也不注册到路由数据源(按需开启)
|
||||
oracle-master:
|
||||
enabled: false
|
||||
driverClassName: oracle.jdbc.OracleDriver
|
||||
url: "${DB_ORACLE_MASTER_URL:jdbc:oracle:thin:@172.16.31.172:1521/SDLYZ}"
|
||||
username: "${DB_ORACLE_MASTER_USERNAME:SDLY_QX}"
|
||||
password: "${DB_ORACLE_MASTER_PASSWORD:jNHnqv3hH7}"
|
||||
initial-size: 5
|
||||
min-idle: 5
|
||||
max-active: 20
|
||||
max-wait: 30000
|
||||
async-init: true
|
||||
keep-alive-between-time-millis: 120000
|
||||
time-between-eviction-runs-millis: 60000
|
||||
min-evictable-idle-time-millis: 180000
|
||||
max-evictable-idle-time-millis: 300000
|
||||
phy-timeout-millis: 25200000
|
||||
validation-query: SELECT 1 FROM DUAL
|
||||
validation-query-timeout: 3
|
||||
test-while-idle: true
|
||||
test-on-borrow: false
|
||||
test-on-return: false
|
||||
keep-alive: true
|
||||
remove-abandoned: true
|
||||
remove-abandoned-timeout: 1800
|
||||
log-abandoned: true
|
||||
break-after-acquire-failure: true
|
||||
time-between-connect-error-millis: 30000
|
||||
pool-prepared-statements: true
|
||||
max-open-prepared-statements: 100
|
||||
max-pool-prepared-statement-per-connection-size: 100
|
||||
connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
|
||||
oracle-slave:
|
||||
enabled: false
|
||||
driverClassName: oracle.jdbc.OracleDriver
|
||||
url: "${DB_ORACLE_SLAVE_URL:jdbc:oracle:thin:@172.16.31.172:1521/SDLYZ}"
|
||||
username: "${DB_ORACLE_SLAVE_USERNAME:SDLY_QX}"
|
||||
password: "${DB_ORACLE_SLAVE_PASSWORD:jNHnqv3hH7}"
|
||||
initial-size: 5
|
||||
min-idle: 5
|
||||
max-active: 20
|
||||
max-wait: 30000
|
||||
async-init: true
|
||||
keep-alive-between-time-millis: 120000
|
||||
time-between-eviction-runs-millis: 60000
|
||||
min-evictable-idle-time-millis: 180000
|
||||
max-evictable-idle-time-millis: 300000
|
||||
phy-timeout-millis: 25200000
|
||||
validation-query: SELECT 1 FROM DUAL
|
||||
validation-query-timeout: 3
|
||||
test-while-idle: true
|
||||
test-on-borrow: false
|
||||
test-on-return: false
|
||||
keep-alive: true
|
||||
remove-abandoned: true
|
||||
remove-abandoned-timeout: 1800
|
||||
log-abandoned: true
|
||||
break-after-acquire-failure: true
|
||||
time-between-connect-error-millis: 30000
|
||||
pool-prepared-statements: true
|
||||
max-open-prepared-statements: 100
|
||||
max-pool-prepared-statement-per-connection-size: 100
|
||||
connection-properties: oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000;oracle.net.READ_TIMEOUT=60000
|
||||
filter:
|
||||
stat:
|
||||
enabled: true
|
||||
|
||||
Loading…
Reference in New Issue
Block a user