SpringBoot-动态切换多数据源

背景

在特征挖掘平台项目中,会遇到在不同的业务线间需要切换不同的数据库去查询原始数据来计算特征,因此需要对多个数据库进行实时切换,这里分享简单的思路及实现方案。


实现思路

利用 Spring 提供的 AbstractRoutingDataSource 抽象出一个 DynamicDataSource 实现动态切换数据源。
data1


实现方案

环境准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.4.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.1</version>
</dependency>

yml 配置

1
2
3
4
5
6
7
8
9
10
11
12
spring:
datasource:
one:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
jdbc-url: jdbc:mysql://127.0.0.1:3306/one?useUnicode=true&characterEncoding=UTF-8
two:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
jdbc-url: jdbc:mysql://127.0.0.1:3306/two?useUnicode=true&characterEncoding=UTF-8

实现

AOP 切面

1
2
3
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CurrentDataSource {}

Aspect 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Aspect
@Order(-1)
@Component
public class CurrentDataSourceAspect {

private static Logger logger = LoggerFactory.getLogger(CurrentDataSourceAspect.class);

@Pointcut("@annotation(com.*.CurrentDataSource)")
public void dsPointCut() {}

@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
List<String> paramNameList = Arrays.asList(signature.getParameterNames());
List<Object> paramList = Arrays.asList(point.getArgs());

CurrentDataSource dataSource = signature.getMethod().getAnnotation(CurrentDataSource.class);
String dataSourceValue = DataSourceType.TYPE_ONE.getName(); // 默认数据源
if (Objects.nonNull(dataSource)) {
for (int i = 0; i < paramNameList.size(); i++) {
if (paramNameList.get(i).equals("param")) { // 根据该参数字段来确定切换数据源
dataSourceValue = (String) paramList.get(i);
logger.info("change datasource: " + dataSourceValue);
}
}
}
DataSourceContext.set(DataSourceType.getInstance(dataSourceValue));

try {
return point.proceed();
} finally {
// 销毁数据源
logger.info("remove datasource: " + dataSourceValue);
DataSourceContext.remove();
}
}

}

DataSourceType 枚举实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Getter
@AllArgsConstructor
public enum DataSourceType {

TYPE_ONE("a"),
TYPE_TWO("b");

private String name;

public static DataSourceType getInstance(String name) {
for (DataSourceType type : DataSourceType.values()) {
if (name.equals(type.getName())) {
return type;
}
}
return DataSourceType.TYPE_ONE; // 默认数据源
}

}

数据源配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@Configuration
public class DataSourceConfig {

/**
* 自定义动态datasource
* @return DataSource
*/
@Bean(name = "dynamicDataSource")
@Primary
public DataSource dataSource(@Qualifier("oneDataSource") DataSource oneDataSource,
@Qualifier("twoDataSource") DataSource twoDataSource) {
DynamicDataSource routingDataSource = new DynamicDataSource();
routingDataSource.initDataSource(oneDataSource, twoDataSource);
return routingDataSource;
}

@Bean(name = "oneDataSource")
@ConfigurationProperties(prefix = "spring.datasource.one")
public DataSource oneDataSource() {
return DataSourceBuilder.create().build();
}

@Bean(name = "twoDataSource")
@ConfigurationProperties(prefix = "spring.datasource.two")
public DataSource twoDataSource() {
return DataSourceBuilder.create().build();
}

/**
* 自定义SqlSessionFactory
*
* @param dataSource 自定义datasource
* @return SqlSessionFactory
* @throws Exception
*/
@Bean(name = "dynamicSqlSessionFactory")
public SqlSessionFactory customSqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setTypeAliasesPackage("com.*.mapper");
Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml");
bean.setMapperLocations(resources);
bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return bean.getObject();
}

/**
* 自定义DataSourceTransactionManager
*
* @param dataSource 自定义datasource
* @return DataSourceTransactionManager
*/
@Bean(name = "dynamicTransactionManager")
@Primary
public DataSourceTransactionManager customTransactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}

/**
* 自定义SqlSessionTemplate
*
* @param sqlSessionFactory 自定义SqlSessionFactory
* @return SqlSessionTemplate
*/
@Bean(name = "dynamicSqlSessionTemplate")
@Primary
public SqlSessionTemplate customSqlSessionTemplate(@Qualifier("dynamicSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}

}

动态数据源切换配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DynamicDataSource extends AbstractRoutingDataSource {

private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

@Override
protected Object determineCurrentLookupKey() {
return DataSourceContext.get();
}

public void initDataSource(DataSource oneDataSource, DataSource twoDataSource) {
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DataSourceType.TYPE_ONE, oneDataSource);
dataSourceMap.put(DataSourceType.TYPE_TWO, twoDataSource);
this.setTargetDataSources(dataSourceMap); // 设置动态数据源
this.setDefaultTargetDataSource(oneDataSource);
}

}

方法上动态切换数据源:

1
2
3
4
5
@Override
@CurrentDataSource
public void test() {
System.out.println("running");
}

引用


个人备注

此博客内容均为作者学习所做笔记,侵删!
若转作其他用途,请注明来源!