动态数据源切换
- 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.DruidDataSourcedruid:db1:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/andy_test_1?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=falseusername: rootpassword: rootdb2:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/andy_test_2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=falseusername: rootpassword: 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 {@Resourceprivate 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);}@Overrideprotected 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")@Primarypublic 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