SpringBoot 多数据源配置

三个测试数据库SQL:

data01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`age` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES (1, '001', 10);
INSERT INTO `tb_user` VALUES (2, '002', 20);

data02

1
2
3
4
5
6
7
8
9
10
11
12
13
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`age` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES (1, '003', 30);
INSERT INTO `tb_user` VALUES (2, '004', 40);

data03

1
2
3
4
5
6
7
8
9
10
11
12
13
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`age` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES (1, '005', 50);
INSERT INTO `tb_user` VALUES (2, '006', 60);

Pom.xml

  • 这是刚新建SpringBoot项目的pom.xml依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
  • 配置数据库和jdbc
1
2
3
4
5
6
7
8
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
  • 配置spring-aop
1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>

application.yml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
datasource:
local:
username: root
password: 123456
jdbc-url: jdbc:mysql://127.0.0.1:3306/data01?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
enabled: true
remote:
username: root
password: 123456
jdbc-url: jdbc:mysql://127.0.0.1:3306/data02?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
three:
username: root
password: 123456
jdbc-url: jdbc:mysql://127.0.0.1:3306/data03?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
enabled: true

注意:各个版本的 springboot 配置 datasource 时参数有所变化,例如低版本配置数据库 url时使用 url 属性,高版本使用 jdbc-url 属性

连接数据源配置文件

1
2
3
4
5
public enum DataSourceType {
REMOTE,
LOCAL,
THREE
}
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
public class DynamicDataSourceContextHolder {

/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本
* 所以每个线程都可以独立的改变自己的副本,而不会影响其它线程所对应的副本
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

/**
* 设置数据源类型
* @param dataSourceType
*/
public static void setDataSource(String dataSourceType){
System.out.printf("切换到{%s}数据源",dataSourceType);
CONTEXT_HOLDER.set(dataSourceType);
}

/**
* 获取数据源类型
* @return
*/
public static String getDataSourceType(){
return CONTEXT_HOLDER.get();
}

/**
* 清空数据源类型
*/
public static void clearDataSourceType(){
CONTEXT_HOLDER.remove();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DynamicDataSource extends AbstractRoutingDataSource {

public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object,Object> targetDataSource){
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSource);
// afterPropertiesSet方法调用时用来将targetDataSource的属性写入resolvedDataSource中的
super.afterPropertiesSet();
}

/**
* 根据Key获取数据源信息
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
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
@Configuration
public class DataSourceConfig {

@Bean
@ConfigurationProperties("spring.datasource.remote")
public DataSource remoteDataSource(){
return DataSourceBuilder.create().build();
}

@Bean
@ConfigurationProperties("spring.datasource.local")
@ConditionalOnProperty(prefix = "spring.datasource.local",name = "enabled",havingValue = "true")
public DataSource localDataSource(){
return DataSourceBuilder.create().build();
}

@Bean
@ConfigurationProperties("spring.datasource.three")
@ConditionalOnProperty(prefix = "spring.datasource.three",name = "enabled",havingValue = "true")
public DataSource threeDataSource(){
return DataSourceBuilder.create().build();
}

@Bean
@Primary //自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
public DynamicDataSource dynamicDataSource(DataSource remoteDataSource,DataSource localDataSource,DataSource threeDataSource){
Map<Object,Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.REMOTE.name(),remoteDataSource);
targetDataSources.put(DataSourceType.LOCAL.name(),localDataSource);
targetDataSources.put(DataSourceType.THREE.name(),threeDataSource);
return new DynamicDataSource(remoteDataSource,targetDataSources);
}
}

自定义注解:

1
2
3
4
5
6
7
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

DataSourceType value() default DataSourceType.REMOTE;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Aspect
@Order(1)
@Component
public class DataSourceAspect {

@Pointcut("@annotation(com.example.demo.mult.DataSource)")
public void dsPointCut(){}

@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable{
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
if (null!=dataSource){
DynamicDataSourceContextHolder.setDataSource(dataSource.value().name());
}
try {
return point.proceed();
}finally {
// 销毁数据源,在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
public class TestController {

@Resource
private JdbcTemplate jdbcTemplate;

@GetMapping("/data1")
@DataSource(value = DataSourceType.LOCAL)
public List<Map<String, Object>> getData1(){
return jdbcTemplate.queryForList("select * from tb_user");
}

@GetMapping("/data2")
@DataSource(value = DataSourceType.REMOTE)
public List<Map<String, Object>> getData2(){
return jdbcTemplate.queryForList("select * from tb_user");
}

@GetMapping("/data3")
@DataSource(value = DataSourceType.THREE)
public List<Map<String, Object>> getData3(){
return jdbcTemplate.queryForList("select * from tb_user");
}
}