动态数据源切换
- 1.环境初始化
- 2.切换数据源代码
- 3.第二节代码的测试
- 4.用注解的方式进行优化
此代码在jdk11上测试通过,SpringBoot版本为2.7.14
1.环境初始化
1.创建两个库
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 表结构
DROP TABLE IF EXISTS `t_stu`;
CREATE TABLE `t_stu` (
`id` int NOT NULL COMMENT 'id',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`age` int NULL DEFAULT NULL,
`sex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`class_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '课程名',
`teacher` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '老师',
`score` decimal(10, 2) NULL DEFAULT NULL COMMENT '分数',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
-- 库1数据
INSERT INTO `t_stu` VALUES (1, 'andy', 10, '男', '语文', '虚竹', 100.00);
-- 库2数据
INSERT INTO `t_stu` VALUES (1, 'lily', 10, '女', '数学', '小龙女', 100.00);
2.pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
3.application.yml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
db1:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/andy_test_1?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: root
db2:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/andy_test_2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: root
4.pojo类
public class Stu implements Serializable {
private Integer id;
private String name;
private Integer age;
private String className;
private BigDecimal score;
// getter...
// setter...
}
5.mapper
@Mapper
public interface StuMapper extends BaseMapper<Stu> {
List<Stu> findAll();
int insertStu(@Param("stu") Stu stu);
}
6.mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.andy.mapper.StuMapper">
<select id="findAll" resultType="com.andy.pojo.Stu">
select * from t_stu
</select>
<insert id="insertStu" parameterType="com.andy.pojo.Stu">
insert into t_stu(
id,
name)
values(
#{stu.id},
#{stu.name})
</insert>
</mapper>
7.controller
@RestController
public class TestController {
@Resource
private StuMapper stuMapper;
@GetMapping("/getData/{datasourceName}")
public String getMasterData(@PathVariable("datasourceName") String datasourceName){
DataSourceAndyContextHolder.setDataSource(datasourceName);
List<Stu> all = stuMapper.findAll();
DataSourceAndyContextHolder.removeDataSource();
return all.get(0).getName();
}
@GetMapping("/insertData/{datasourceName}")
@Transactional // spring的事务注解,在本案例中不会生效
public void insertData(@PathVariable("datasourceName") String datasourceName, @RequestBody Stu stu){
DataSourceAndyContextHolder.setDataSource(datasourceName);
stuMapper.insertStu(stu);
// int i = 3/0; 这一行是为了测试事务,各位大佬可以自行测似
DataSourceAndyContextHolder.removeDataSource();
}
}
2.切换数据源代码
1.先来个存储数据源的map
public class DataSourceAndyContextHolder {
// 此类提供线程局部变量。这些变量不同于它们的正常对应关系是每个线程访问一个线程(通过get、set方法),有自己的独立初始化变量的副本。
private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();
/**
* 设置数据源
* @param dataSourceName 数据源名称
*/
public static void setDataSource(String dataSourceName){
DATASOURCE_HOLDER.set(dataSourceName);
}
/**
* 获取当前线程的数据源
* @return 数据源名称
*/
public static String getDataSource(){
return DATASOURCE_HOLDER.get();
}
/**
* 删除当前数据源
*/
public static void removeDataSource(){
DATASOURCE_HOLDER.remove();
}
}
2.实现动态切换数据源
public class DynamicDataAndySource extends AbstractRoutingDataSource {
public DynamicDataAndySource(DataSource defaultDataSource, Map<Object, Object> targetDataSources){
super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(targetDataSources);
}
@Override
protected Object determineCurrentLookupKey() {
return DataSourceAndyContextHolder.getDataSource();
}
}
3.设置数据源
@Configuration
public class DateSourceAndyConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.db1")
public DataSource masterDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.db2")
public DataSource slaveDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataAndySource createDynamicDataSource(){
Map<Object,Object> dataSourceMap = new HashMap<>();
DataSource defaultDataSource = masterDataSource();
dataSourceMap.put("db1",defaultDataSource);
dataSourceMap.put("db2",slaveDataSource());
return new DynamicDataAndySource(defaultDataSource,dataSourceMap);
}
}
3.第二节代码的测试
1.测试db1库
127.0.0.1:8080/getData/db1
2.测试db2库
127.0.0.1:8080/getData/db2
4.用注解的方式进行优化
1.导入坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.定义一个注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface AndyDS {
String value() default "db1";
}
3.定义切面
@Aspect
@Component
@Slf4j
public class DSAspect {
@Pointcut("@annotation(com.andy.config.AndyDS)")
public void dynamicDataSource(){}
@Around("dynamicDataSource()")
public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature)point.getSignature();
Method method = signature.getMethod();
AndyDS ds = method.getAnnotation(AndyDS.class);
if (Objects.nonNull(ds)){
DataSourceAndyContextHolder.setDataSource(ds.value());
}
try {
return point.proceed();
} finally {
DataSourceAndyContextHolder.removeDataSource();
}
}
}
4.测试代码
@GetMapping("/getData_2")
@AndyDS("db2")
public List<Stu> getData_1(){
List<Stu> all = stuMapper.findAll();
return all;
}
5.测试结果
127.0.0.1:8080/getData_2