springboot druid连接池实现多数据源动态切换方式
一、继承接口AbstractRoutingDataSource(mysql)
主要的starter:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
yml中的配置文件:
server: port: 8888 spring: datasource: # url: jdbc:mysql://127.0.0.1:3306/db01?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false # username: root # password: 123456 # driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource datasource1: url: jdbc:mysql://127.0.0.1:3306/db01?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false username: root password: 123456 initial-size: 1 min-idle: 1 max-active: 20 test-on-borrow: true driver-class-name: com.mysql.cj.jdbc.Driver datasource2: url: jdbc:mysql://127.0.0.1:3306/db02?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false username: root password: 123456 initial-size: 1 min-idle: 1 max-active: 20 test-on-borrow: true driver-class-name: com.mysql.cj.jdbc.Driver datasource3: url: jdbc:mysql://127.0.0.1:3306/db03?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false username: root password: 123456 initial-size: 1 min-idle: 1 max-active: 20 test-on-borrow: true driver-class-name: com.mysql.cj.jdbc.Driver # \u8FD9\u79CD\u65B9\u5F0F\u4E0D\u80FD\u8BBE\u7F6Emybatis plugin #mybatis: # configuration: # interceptors: # - com.tuling.dynamic.datasource.plugin.DynamicDataSourcePlugin
配置文件:将对应的数据源配置为Bean,自动管理
@Configuration public class DynamicDataSourceConfig{ //定义datasource:因为是数据源,所以返回结果为DataSource @Bean @ConfigurationProperties(prefix = "spring.datasource.datasource1") public DataSource dataSource1(){ // 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.datasource2") public DataSource dataSource2(){ return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.datasource3") public DataSource dataSource3(){ return DruidDataSourceBuilder.create().build(); } @Bean public DataSourceTransactionManager dataSourceTransactionManager1(@Qualifier("dataSource1") DataSource dataSource1) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource1); return dataSourceTransactionManager; } @Bean public DataSourceTransactionManager dataSourceTransactionManager2(@Qualifier("dataSource2") DataSource dataSource2) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource2); return dataSourceTransactionManager; } @Bean public DataSourceTransactionManager dataSourceTransactionManager3(@Qualifier("dataSource3") DataSource dataSource3) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource3); return dataSourceTransactionManager; } }
继承(AbstractRoutingDataSource),并进行配置管理
@Component //这里使用@Primary,才会在程序运行过程中最先使用该类 @Primary public class DynamicDataSource extends AbstractRoutingDataSource { public static ThreadLocal<String> name = new ThreadLocal<>(); @Resource DataSource dataSource1; @Resource DataSource dataSource2; @Resource DataSource dataSource3; /** * Determine the current lookup key. This will typically be * implemented to check a thread-bound transaction context. * <p>Allows for arbitrary keys. The returned key needs * to match the stored lookup key type, as resolved by the * {@link #resolveSpecifiedLookupKey} method. */ @Override protected Object determineCurrentLookupKey() { return name.get(); } @Override public void afterPropertiesSet() { // 为targetDataSources初始化所有数据源 Map<Object, Object> targetDataSource = new HashMap<>(); targetDataSource.put("R1", dataSource1); targetDataSource.put("R2", dataSource2); targetDataSource.put("R3", dataSource3); super.setTargetDataSources(targetDataSource); //为defaultTargetDataSource 设置默认的数据源 super.setDefaultTargetDataSource(dataSource1); //返回给父类,父类进行数据源的初始化和管理 super.afterPropertiesSet(); } }
最后在选择自己对应的数据源时,可以根据自己设置的Key值,获取对应的数据源连接
@GetMapping("/select1") public List<UserInfo> queryUserById01(){ DynamicDataSource.name.set("R1");//这里“R1”就表示当前使用的数据源key为“R1” List<UserInfo> userInfos = userService.selectByPrimaryKey(); return userInfos; }
二、使用@注解加aop动态代理
需要引入的starter
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.4.5</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency>
(1)、定义注解
@Target({ElementType.METHOD,ElementType.TYPE}) //保留级别: //SOURCE:注释将被编译器丢弃 //RUNTIME:注释将由编译器记录在类文件中,并在运行时由 VM 保留,因此可以反射性地读取它们 //CLASS:注释将由编译器记录在类文件中,但不需要在运行时由 VM 保留。这是默认行为。 @Retention(RetentionPolicy.RUNTIME) public @interface TargetDataSource { String value() default "R1"; }
(2)定义注解的AOP切面
@Component @Aspect public class DynamicDataSourceAspect { //within(com.zjl.datasources.controller.*)表示在项目中,controller下的所有包使用都会被扫描到,也可以不使用 //@annotation(ts)表示是注解的切面 //(JoinPoint point,TargetDataSource ts)表示切入点和扫描到的注解类(ts.value()注解中的value对象) //@Before("within(com.zjl.datasources.controller.*) && @annotation(ts)") @Before("@annotation(ts)") public void before(JoinPoint point, TargetDataSource ts){ String value = ts.value(); DynamicDataSource.name.set(value); System.out.println(value); } }
/** *注意在主类中引入自动扫描配置容器和允许切面生成AOp代理 */ @SpringBootApplication(exclude= {DataSourceAutoConfiguration.class}) @MapperScan("com.zjl.datasources.mapper") @ComponentScan(basePackages = {"com.zjl.datasources.*"}) @EnableAspectJAutoProxy(exposeProxy=true) public class DatasourcesApplication { public static void main(String[] args) { SpringApplication.run(DatasourcesApplication.class, args); } }
(3)多数据源Bean配置(配置类)
@Configuration public class DynamicDataSourceConfig{ //定义datasource @Bean @ConfigurationProperties(prefix = "spring.datasource.datasource1") public DataSource dataSource1(){ // 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.datasource2") public DataSource dataSource2(){ return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.datasource3") public DataSource dataSource3(){ return DruidDataSourceBuilder.create().build(); } @Bean public DataSourceTransactionManager dataSourceTransactionManager1(@Qualifier("dataSource1") DataSource dataSource1) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource1); return dataSourceTransactionManager; } @Bean public DataSourceTransactionManager dataSourceTransactionManager2(@Qualifier("dataSource2") DataSource dataSource2) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource2); return dataSourceTransactionManager; } @Bean public DataSourceTransactionManager dataSourceTransactionManager3(@Qualifier("dataSource3") DataSource dataSource3) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource3); return dataSourceTransactionManager; } //使用mybatis的bean //@Bean //public Interceptor dynamicDataSourcePlugin(){ // return new DynamicDataSourcePlugin(); //} }
(4)动态多数据源配置
@Component //这里使用@Primary,才会在程序运行过程中最先使用该类 @Primary public class DynamicDataSource extends AbstractRoutingDataSource { public static ThreadLocal<String> name = new ThreadLocal<>(); @Resource DataSource dataSource1; @Resource DataSource dataSource2; @Resource DataSource dataSource3; /** * Determine the current lookup key. This will typically be * implemented to check a thread-bound transaction context. * <p>Allows for arbitrary keys. The returned key needs * to match the stored lookup key type, as resolved by the * {@link #resolveSpecifiedLookupKey} method. */ @Override protected Object determineCurrentLookupKey() { return name.get(); } @Override public void afterPropertiesSet() { // 为targetDataSources初始化所有数据源 Map<Object, Object> targetDataSource = new HashMap<>(); targetDataSource.put("R1", dataSource1); targetDataSource.put("R2", dataSource2); targetDataSource.put("R3", dataSource3); super.setTargetDataSources(targetDataSource); //为defaultTargetDataSource 设置默认的数据源 super.setDefaultTargetDataSource(dataSource1); //返回给父类,父类进行数据源的初始化和管理 super.afterPropertiesSet(); } }
使用注解指定对应的数据源
@GetMapping("/select1") @TargetDataSource("R3") public List<UserInfo> queryUserById01(){ //DynamicDataSource.name.set("R1"); List<UserInfo> userInfos = userService.selectByPrimaryKey(); return userInfos; }