This commit is contained in:
kk
2026-07-07 18:01:21 +08:00
commit b259f94d4b
1088 changed files with 121778 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springblade</groupId>
<artifactId>BladeX-Tool</artifactId>
<version>${revision}</version>
</parent>
<artifactId>blade-starter-db-dynamic</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<properties>
<module.name>org.springblade.blade.starter.db.dynamic</module.name>
</properties>
<dependencies>
<!-- BladeX -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-tool</artifactId>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
</dependency>
<!--Dynamic-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
</dependency>
<!-- Auto -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,169 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.db.dynamic.config;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationAdvisor;
import com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptor;
import com.baomidou.dynamic.datasource.aop.DynamicLocalTransactionInterceptor;
import com.baomidou.dynamic.datasource.creator.DataSourceCreator;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.event.DataSourceInitEvent;
import com.baomidou.dynamic.datasource.event.EncDataSourceInitEvent;
import com.baomidou.dynamic.datasource.processor.DsJakartaHeaderProcessor;
import com.baomidou.dynamic.datasource.processor.DsJakartaSessionProcessor;
import com.baomidou.dynamic.datasource.processor.DsProcessor;
import com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor;
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceCreatorAutoConfiguration;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDatasourceAopProperties;
import lombok.RequiredArgsConstructor;
import org.springblade.core.db.dynamic.processor.DynamicDataSourceProcessor;
import org.springblade.core.db.dynamic.provider.DynamicDataSourceAutoProvider;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Role;
import org.springframework.context.expression.BeanFactoryResolver;
import javax.sql.DataSource;
import java.util.List;
import java.util.Optional;
/**
* 动态数据源配置类
*
* @author BladeX
*/
@RequiredArgsConstructor
@EnableConfigurationProperties({DataSourceProperties.class, DynamicDataSourceProperties.class})
@AutoConfiguration(before = {DruidDataSourceAutoConfigure.class, DynamicDataSourceAutoConfiguration.class})
@Import(value = {DynamicDataSourceCreatorAutoConfiguration.class})
public class DynamicDataSourceConfiguration {
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
@ConditionalOnMissingBean
public DsProcessor dsProcessor(BeanFactory beanFactory) {
DsProcessor headerProcessor = new DsJakartaHeaderProcessor();
DsProcessor sessionProcessor = new DsJakartaSessionProcessor();
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
spelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory));
headerProcessor.setNextProcessor(sessionProcessor);
sessionProcessor.setNextProcessor(spelExpressionProcessor);
return headerProcessor;
}
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX + ".aop", name = "enabled", havingValue = "true", matchIfMissing = true)
public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor, DynamicDataSourceProperties dynamicDataSourceProperties) {
DynamicDatasourceAopProperties aopProperties = dynamicDataSourceProperties.getAop();
DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(aopProperties.getAllowedPublicOnly(), dsProcessor);
DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor, DS.class);
advisor.setOrder(aopProperties.getOrder());
return advisor;
}
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "seata", havingValue = "false", matchIfMissing = true)
public DynamicDataSourceAnnotationAdvisor dynamicTransactionAdvisor(DynamicDataSourceProperties dynamicDataSourceProperties) {
DynamicDatasourceAopProperties aopProperties = dynamicDataSourceProperties.getAop();
DynamicLocalTransactionInterceptor interceptor = new DynamicLocalTransactionInterceptor(aopProperties.getAllowedPublicOnly());
return new DynamicDataSourceAnnotationAdvisor(interceptor, DSTransactional.class);
}
@Bean
@ConditionalOnMissingBean
public DataSourceInitEvent dataSourceInitEvent() {
return new EncDataSourceInitEvent();
}
@Bean
@ConditionalOnMissingBean
public DefaultDataSourceCreator dataSourceCreator(List<DataSourceCreator> dataSourceCreators,
DataSourceInitEvent dataSourceInitEvent,
DynamicDataSourceProperties properties) {
DefaultDataSourceCreator creator = new DefaultDataSourceCreator();
creator.setCreators(dataSourceCreators);
creator.setDataSourceInitEvent(dataSourceInitEvent);
creator.setPublicKey(properties.getPublicKey());
creator.setLazy(properties.getLazy());
creator.setP6spy(properties.getP6spy());
creator.setSeata(properties.getSeata());
creator.setSeataMode(properties.getSeataMode());
return creator;
}
@Bean
@Primary
public DataSource dataSource(List<DynamicDataSourceProvider> providers,
DynamicDataSourceProperties dynamicDataSourceProperties) {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource(providers);
dataSource.setPrimary(dynamicDataSourceProperties.getPrimary());
dataSource.setStrict(dynamicDataSourceProperties.getStrict());
dataSource.setStrategy(dynamicDataSourceProperties.getStrategy());
dataSource.setP6spy(dynamicDataSourceProperties.getP6spy());
dataSource.setSeata(dynamicDataSourceProperties.getSeata());
return dataSource;
}
@Bean
@Primary
public DynamicDataSourceProvider dynamicDataSourceProvider(DefaultDataSourceCreator dataSourceCreator,
DataSourceProperties dataSourceProperties,
DynamicDataSourceProperties dynamicDataSourceProperties,
List<DynamicDataSourceProcessor> processors) {
String driverClassName = dataSourceProperties.getDriverClassName();
String url = dataSourceProperties.getUrl();
String username = dataSourceProperties.getUsername();
String password = dataSourceProperties.getPassword();
DataSourceProperty master = dynamicDataSourceProperties.getDatasource().get(dynamicDataSourceProperties.getPrimary());
if (master != null) {
driverClassName = Optional.ofNullable(master.getDriverClassName()).orElse(driverClassName);
url = master.getUrl();
username = master.getUsername();
password = master.getPassword();
}
return new DynamicDataSourceAutoProvider(dataSourceCreator, dynamicDataSourceProperties, driverClassName, url, username, password, processors);
}
}
@@ -0,0 +1,55 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.db.dynamic.creator;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import javax.sql.DataSource;
/**
* 动态数据源配置类
*
* @author BladeX
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class DynamicDataSourceProperty extends DataSourceProperty {
/**
* 租户id
*/
private String tenantId;
/**
* 自定义数据源
*/
private DataSource dataSource;
}
@@ -0,0 +1,60 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.db.dynamic.env;
import org.springblade.core.auto.annotation.AutoEnvPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* 初始化分库分表配置
*
* @author BladeX
*/
@AutoEnvPostProcessor
public class DynamicDataSourceEnvPostProcessor implements EnvironmentPostProcessor, Ordered {
private static final String DYNAMIC_DATASOURCE_KEY = "spring.datasource.dynamic.enabled";
private static final String DYNAMIC_FILTER_KEY = "spring.datasource.dynamic.druid.proxy-filters[0]";
private static final String AUTOCONFIGURE_EXCLUDE_KEY = "spring.autoconfigure.exclude";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
environment.getSystemProperties().put(DYNAMIC_DATASOURCE_KEY, "false");
environment.getSystemProperties().put(DYNAMIC_FILTER_KEY, "sqlLogInterceptor");
environment.getSystemProperties().put(AUTOCONFIGURE_EXCLUDE_KEY, "com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure");
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
@@ -0,0 +1,52 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.db.dynamic.exception;
/**
* 动态数据源异常
*
* @author BladeX
*/
public class DynamicDataSourceException extends RuntimeException {
public DynamicDataSourceException(String message) {
super(message);
}
/**
* 提高性能
*
* @return Throwable
*/
@Override
public Throwable fillInStackTrace() {
return this;
}
public Throwable doFillInStackTrace() {
return super.fillInStackTrace();
}
}
@@ -0,0 +1,43 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.db.dynamic.holder;
/**
* 动态数据源持有者处理接口
* <p>
* 其他模块可以实现此接口来提供自定义的数据源动态创建逻辑
*
* @author BladeX
*/
public interface DynamicDataSourceHolder {
/**
* 加载数据源
*
* @param dbName 数据源名称
*/
void handleDataSource(String dbName);
}
@@ -0,0 +1,78 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.db.dynamic.processor;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
/**
* 动态数据源扩展接口
* <p>
* 其他模块可以实现此接口来提供自定义的数据源启动创建逻辑
*
* @author BladeX
*/
public interface DynamicDataSourceProcessor {
/**
* 是否启用该扩展
* <p>
* 可以通过配置文件或条件判断来控制是否启用
*
* @return true表示启用,false表示禁用
*/
default boolean isEnabled() {
return true;
}
/**
* 获取优先级
* <p>
* 数值越小优先级越高,默认值为 100
*
* @return 优先级数值
*/
default int getOrder() {
return 100;
}
/**
* 执行自定义数据源创建逻辑
* <p>
* 通过执行SQL语句或其他方式获取数据源配置信息,
* 并构建对应的数据源属性映射
*
* @param statement 数据库连接语句对象
* @param dynamicDataSourceProperties 动态数据源配置属性
* @return 数据源配置映射,key为数据源名称,value为数据源属性
* @throws SQLException 数据库操作异常
*/
Map<String, DataSourceProperty> loadDataSourceProperties(Statement statement, DynamicDataSourceProperties dynamicDataSourceProperties) throws SQLException;
}
@@ -0,0 +1,219 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.db.dynamic.provider;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.creator.druid.DruidConfig;
import com.baomidou.dynamic.datasource.provider.AbstractJdbcDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.baomidou.dynamic.datasource.toolkit.DsStrUtils;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.db.dynamic.creator.DynamicDataSourceProperty;
import org.springblade.core.db.dynamic.processor.DynamicDataSourceProcessor;
import org.springblade.core.db.dynamic.utils.DataSourceUtil;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.stream.Collectors;
/**
* 动态数据源初始加载
*
* @author BladeX
*/
@Slf4j
public class DynamicDataSourceAutoProvider extends AbstractJdbcDataSourceProvider {
private final String driverClassName;
private final String url;
private final String username;
private final String password;
private final DefaultDataSourceCreator dataSourceCreator;
private final DynamicDataSourceProperties dynamicDataSourceProperties;
private final List<DynamicDataSourceProcessor> processors;
public DynamicDataSourceAutoProvider(DefaultDataSourceCreator dataSourceCreator, DynamicDataSourceProperties dynamicDataSourceProperties, String driverClassName, String url, String username, String password) {
this(dataSourceCreator, dynamicDataSourceProperties, driverClassName, url, username, password, Collections.emptyList());
}
public DynamicDataSourceAutoProvider(DefaultDataSourceCreator dataSourceCreator, DynamicDataSourceProperties dynamicDataSourceProperties, String driverClassName, String url, String username, String password, List<DynamicDataSourceProcessor> processors) {
super(dataSourceCreator, driverClassName, url, username, password);
this.dataSourceCreator = dataSourceCreator;
this.dynamicDataSourceProperties = dynamicDataSourceProperties;
this.driverClassName = driverClassName;
this.url = url;
this.username = username;
this.password = password;
this.processors = Optional.ofNullable(processors)
.map(ext -> ext.stream()
.filter(DynamicDataSourceProcessor::isEnabled)
.sorted(Comparator.comparingInt(DynamicDataSourceProcessor::getOrder))
.collect(Collectors.toList()))
.orElse(Collections.emptyList());
}
@Override
protected Map<String, DataSourceProperty> executeStmt(Statement statement) {
// 构建数据源集合
Map<String, DataSourceProperty> map = new HashMap<>(16);
// 构建主数据源
DruidConfig druid = dynamicDataSourceProperties.getDruid();
druid.setProxyFilters("sqlLogInterceptor");
DataSourceProperty masterProperty = new DataSourceProperty();
masterProperty.setDriverClassName(driverClassName);
masterProperty.setUrl(url);
masterProperty.setUsername(username);
masterProperty.setPassword(password);
masterProperty.setDruid(druid);
map.put(dynamicDataSourceProperties.getPrimary(), masterProperty);
// 构建yml数据源
Map<String, DataSourceProperty> datasource = dynamicDataSourceProperties.getDatasource();
if (!datasource.isEmpty()) {
map.putAll(datasource);
}
// 执行数据源扩展逻辑
executeProcessors(statement, map);
return map;
}
/**
* 执行所有数据源扩展
*
* @param statement 数据库连接语句对象
* @param dataSourceMap 数据源映射集合
*/
private void executeProcessors(Statement statement, Map<String, DataSourceProperty> dataSourceMap) {
// 如果没有配置任何数据源扩展,则直接返回
if (processors.isEmpty()) {
log.warn("No datasource processors found");
return;
}
// 遍历所有数据源扩展并执行
processors.forEach(extension -> {
try {
Map<String, DataSourceProperty> customDataSources = extension.loadDataSourceProperties(statement, dynamicDataSourceProperties);
if (customDataSources != null && !customDataSources.isEmpty()) {
dataSourceMap.putAll(customDataSources);
}
} catch (Exception e) {
log.error("Exception occurred while executing extension", e);
}
});
}
@Override
public Map<String, DataSource> loadDataSources() {
Connection conn = null;
Statement stmt = null;
try {
// 由于 SPI 的支持,现在已无需显示加载驱动了
// 但在用户显示配置的情况下,进行主动加载
if (!DsStrUtils.isEmpty(driverClassName)) {
Class.forName(driverClassName);
log.info("Database driver loaded successfully");
}
conn = DriverManager.getConnection(url, username, password);
log.info("Database connection established");
stmt = conn.createStatement();
Map<String, DataSourceProperty> dataSourcePropertiesMap = executeStmt(stmt);
return createDataSourceMap(dataSourcePropertiesMap);
} catch (Exception e) {
log.error("Failed to establish database connection", e);
} finally {
closeResource(conn);
closeResource(stmt);
}
return null;
}
/**
* 关闭资源
*
* @param con 资源
*/
private static void closeResource(AutoCloseable con) {
if (con != null) {
try {
con.close();
} catch (SQLException ex) {
log.error("Failed to close connection", ex);
} catch (Throwable ex) {
log.error("Exception occurred while closing connection", ex);
}
}
}
@Override
protected Map<String, DataSource> createDataSourceMap(Map<String, DataSourceProperty> dataSourcePropertiesMap) {
Map<String, DataSource> dataSourceMap = new HashMap<>(dataSourcePropertiesMap.size() * 2);
String primary = dynamicDataSourceProperties.getPrimary();
for (Map.Entry<String, DataSourceProperty> item : dataSourcePropertiesMap.entrySet()) {
String dsName = item.getKey();
DataSourceProperty dataSourceProperty = item.getValue();
String poolName = dataSourceProperty.getPoolName();
if (StringUtil.isBlank(poolName)) {
poolName = dsName;
}
DynamicDataSourceProperty dynamicDataSourceProperty = BeanUtil.copyProperties(dataSourceProperty, DynamicDataSourceProperty.class);
DataSource dataSource = Objects.requireNonNull(dynamicDataSourceProperty).getDataSource();
if (dataSource == null) {
dataSourceProperty.setPoolName(poolName);
// 主数据源已在 loadDataSources 阶段完成连通性校验,跳过重复测试
if (dsName.equals(primary)) {
log.info("Primary datasource [{}] already verified, creating datasource", dsName);
dataSourceMap.put(dsName, dataSourceCreator.createDataSource(dataSourceProperty));
continue;
}
// 数据库连接测试 - 获取连接参数并验证
String driverClassName = Func.toStr(dataSourceProperty.getDriverClassName(), this.driverClassName);
String url = dataSourceProperty.getUrl();
String username = dataSourceProperty.getUsername();
String password = dataSourceProperty.getPassword();
// 进行连接测试,如果失败则跳过此数据源的创建
if (!DataSourceUtil.dbTest(driverClassName, url, username, password)) {
log.warn("Connection link failed for datasource [{}], skipping creation", dsName);
continue;
}
log.info("Connection link passed for datasource [{}], creating datasource", dsName);
dataSourceMap.put(dsName, dataSourceCreator.createDataSource(dataSourceProperty));
} else {
log.info("Custom datasource [{}] detected, creating datasource", dsName);
dataSourceMap.put(dsName, dataSource);
}
}
return dataSourceMap;
}
}
@@ -0,0 +1,129 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.db.dynamic.utils;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.db.dynamic.exception.DynamicDataSourceException;
import org.springblade.core.db.dynamic.holder.DynamicDataSourceHolder;
import org.springblade.core.tool.utils.StringUtil;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.function.Supplier;
/**
* 数据源工具类
*
* @author BladeX
*/
@Slf4j
public class DataSourceUtil {
/**
* 动态切换数据源并处理
*
* @param dbName 数据源名称
* @param supplier supplier
* @param <R> 泛型
* @return R 函数返回
*/
public static <R> R use(String dbName, Supplier<R> supplier) {
return use(dbName, null, supplier);
}
/**
* 动态切换数据源并处理
*
* @param dbName 数据源名称
* @param holder 数据源持有者
* @param supplier supplier
* @param <R> 泛型
* @return R 函数返回
*/
public static <R> R use(String dbName, DynamicDataSourceHolder holder, Supplier<R> supplier) {
try {
if (StringUtil.isNotBlank(dbName)) {
if (holder != null) {
holder.handleDataSource(dbName);
}
DynamicDataSourceContextHolder.push(dbName);
}
return supplier.get();
} catch (Exception exception) {
throw new DynamicDataSourceException(exception.getMessage());
} finally {
if (StringUtil.isNotBlank(dbName)) {
DynamicDataSourceContextHolder.poll();
}
}
}
/**
* 测试数据库连接
*
* @param driverClass 数据库驱动类
* @param url 数据库连接地址
* @param username 用户名
* @param password 密码
* @return Boolean 连接测试结果
*/
public static Boolean dbTest(String driverClass, String url, String username, String password) {
Connection conn = null;
try {
//测试驱动类
Class.forName(driverClass);
//创建连接
conn = DriverManager.getConnection(url, username, password);
conn.setAutoCommit(Boolean.FALSE);
return true;
} catch (Exception ex) {
log.warn("Database connection test failed: {}", ex.getMessage());
return false;
} finally {
//关闭连接
dbClose(conn);
}
}
/**
* 关闭数据库链接
*
* @param conn 数据库连接
*/
private static void dbClose(Connection conn) {
try {
//关闭数据源连接
if (conn != null) {
conn.close();
}
} catch (Exception ex) {
log.error(ex.getMessage());
}
}
}