init
This commit is contained in:
@@ -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>
|
||||
+169
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -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;
|
||||
|
||||
}
|
||||
+60
@@ -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;
|
||||
}
|
||||
}
|
||||
+52
@@ -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();
|
||||
}
|
||||
}
|
||||
+43
@@ -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);
|
||||
}
|
||||
+78
@@ -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;
|
||||
}
|
||||
+219
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+129
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user