项目地址
功能展示
- 引入
<dependency>
<groupId>com.paper.tiger</groupId>
<artifactId>multipart-datasource-spring-star</artifactId>
<version>1.0.1-SNAPSHOT</version>
</dependency>
- 配置
spring.datasource.dynamic.source.master.username=
spring.datasource.dynamic.source.master.password=
spring.datasource.dynamic.source.master.url=
spring.datasource.dynamic.source.master.driver=
spring.datasource.dynamic.source.slave.username=
spring.datasource.dynamic.source.slave.password=
spring.datasource.dynamic.source.slave.url=
spring.datasource.dynamic.source.slave.driver=
+ 动态添加即可
spring.datasource.dynamic.source.*.username=
spring.datasource.dynamic.source.*.password=
spring.datasource.dynamic.source.*.url=
spring.datasource.dynamic.source.*.driver=com.mysql.jdbc.Driver
其中master为必须,其他数据源根据需要动态添加即可
功能实现
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
配置模块(省略get和set)
public class DataSourceConfig
{
/**用户名*/
private String username;
/**密码*/
private String password;
/**连接url*/
private String url;
/**驱动*/
private String driver;
- 真正引入配置的类
@Order(50)
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.dynamic")
public class DataSourceConfigProperties
{
private Map<String, DataSourceConfig> source = new LinkedHashMap<>();
}
- 编写描述文件(可以在写配置的时候自动提示和点击配置跳转)
文件内容
{
"hints": [],
"groups": [
{
"sourceType": "com.paper.tiger.datasource.common.DataSourceConfigProperties",
"name": "spring.datasource.dynamic",
"type": "com.paper.tiger.datasource.common.DataSourceConfigProperties"
}
],
"properties": [
{
"sourceType": "com.paper.tiger.datasource.common.DataSourceConfigProperties",
"name": "spring.datasource.dynamic.source",
"type": "java.util.Map<java.lang.String,com.paper.tiger.datasource.common.DataSourceConfig>"
}
]
}
aop 动态拦截
- 注解类
该注解可以使用到class上和方法上,value填写数据源的配置名称即可。
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource
{
/**
* 默认数据源为 master
* @return
*/
String value() default "master";
}
- 数据库路由
这里父类其实是一个父类的模版方法模式,我们这里提供的钩子方法determineCurrentLookupKey,在进行数据库选择的时候调用此方法,拿到真正想要操作的数据源。
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
@Override
protected Object determineCurrentLookupKey() {
return contextHolder.get();
}
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clear() {
contextHolder.remove();
}
}
- 指定需要拦截的地方
public class DataSourceAnnotionAdvisor extends AbstractPointcutAdvisor
{
private Advice advice;
private Pointcut pointcut;
public DataSourceAnnotionAdvisor(DataSourceAnnotionMethodIntercept advice)
{
this.advice = advice;
// 类或者是方法上面有DataSource注解,就把他们加入到advisor
Pointcut pointcut1 = new AnnotationMatchingPointcut(DataSource.class, true);
Pointcut pointcut = AnnotationMatchingPointcut
.forMethodAnnotation(DataSource.class);
this.pointcut = new ComposablePointcut(pointcut).union(pointcut1);
}
@Override
public Pointcut getPointcut()
{
return pointcut;
}
@Override
public Advice getAdvice()
{
return advice;
}
}
- 执行数据库切换的aop
public class DataSourceAnnotionMethodIntercept implements MethodInterceptor
{
private static final Logger log = LoggerFactory.getLogger(DataSourceAnnotionMethodIntercept.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable
{
Method method = invocation.getMethod();
DataSource ds = method.getAnnotation(DataSource.class);
// 如果方法上面没有 获取类上的 注解
if (ds == null){
ds = method.getDeclaringClass().getAnnotation(DataSource.class);
}
if (ds == null) {
DynamicDataSource.setDataSource("master");
log.debug("set datasource is master" );
} else {
DynamicDataSource.setDataSource(ds.value());
log.debug("set datasource is " + ds.value());
}
try {
return invocation.proceed();
} finally {
DynamicDataSource.clear();
log.debug("clean datasource");
}
}
}
- 自动化配置类
@Configuration
@AutoConfigureBefore(value = DataSourceAutoConfiguration.class)// 在springboot自带配置前加载
@EnableConfigurationProperties(value = DataSourceConfigProperties.class)// 引入配置
public class MultipartDataSourceAutoConfiguration implements InitializingBean
{
private static final Logger log = LoggerFactory.getLogger(MultipartDataSourceAutoConfiguration.class);
@Autowired
private DataSourceConfigProperties dataSourceConfigProperties;
private DataSource defaultDataSource;
private Map<Object, Object> targetDataSources = new HashMap<>();
@Override
public void afterPropertiesSet()
{
// 加载数据源配置,创建对应的数据源
Map<String, DataSourceConfig> source = dataSourceConfigProperties.getSource();
source.forEach((sourceName,sourceConfig)->{
HikariConfig config = new HikariConfig();
config.setUsername(sourceConfig.getUsername());
config.setPassword(sourceConfig.getPassword());
config.setJdbcUrl(sourceConfig.getUrl());
config.setDriverClassName(sourceConfig.getDriver());
config.setPoolName(sourceName+"PoolName");
HikariDataSource dataSource = new HikariDataSource(config);
if (sourceName.equals("master")){
setDefaultDataSource(dataSource);
}
targetDataSources.put(sourceName,dataSource);
});
}
@Bean
@ConditionalOnMissingBean
@Primary
public DynamicDataSource dynamicDataSource()
{
log.info("开始创建动态数据源,创建参数为:defaultDataSource:{},targetDataSources:{}",defaultDataSource,targetDataSources);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
@Bean
@ConditionalOnMissingBean
public DataSourceAnnotionMethodIntercept dataSourceAnnotionMethodIntercept() {
DataSourceAnnotionMethodIntercept intercept = new DataSourceAnnotionMethodIntercept();
return intercept;
}
@Bean
@ConditionalOnMissingBean
public DataSourceAnnotionAdvisor dataSourceAnnotionAdvisor(@Autowired DataSourceAnnotionMethodIntercept dataSourceAnnotionMethodIntercept) {
DataSourceAnnotionAdvisor annotionAdvisor = new DataSourceAnnotionAdvisor(dataSourceAnnotionMethodIntercept);
return annotionAdvisor;
}
public Map<Object, Object> getTargetDataSources()
{
return targetDataSources;
}
public void setTargetDataSources(Map<Object, Object> targetDataSources)
{
this.targetDataSources = targetDataSources;
}
public DataSource getDefaultDataSource()
{
return defaultDataSource;
}
public void setDefaultDataSource(DataSource defaultDataSource)
{
this.defaultDataSource = defaultDataSource;
}
}
- 编写 spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.paper.tiger.datasource.config.MultipartDataSourceAutoConfiguration
完成
这样就完成了一个动态多数据源的配置,添加数据源和删减数据源直接在配置文件修改即可。