前序

因为spring内置了一个AbstractRoutingDataSource,它可以把多个数据源存到一个Map中,根据不同的key获取不同的数据源,从而实现动态切换数据源效果


1.本地创建两个数据库并创建 user 表供测试使用

动态切换USB descriptor_java

2.创建工程并配置双数据源
# 配置数据源
spring:
  datasource:
    dynamic:
      primary: learn
      datasource:
        learn:
          driver-class-name: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql:///learn?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
          username: root
          password: root
        learnslave:
          driver-class-name: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql:///learn_slave?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
          username: root
          password: root
3.创建controller,service,impl,mapper
@SpringBootApplication
//配置mapper路径
@MapperScan({"com.ziye.mapper"})
public class DynamicDatasourceApplication {
    public static void main(String[] args) {
        SpringApplication.run(DynamicDatasourceApplication.class, args);
    }
}
public interface UserService {
    List<User> findAllUser();
}
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> findAllUser() {
        List<User> list = userMapper.findAllUser();
        System.out.println(list.toString());
        return list;
    }
}
public interface UserMapper {
    @Select("select id,username from user")
    List<User> findAllUser();
}
4.创建自定义注解类并添加到方法上
//表示该注解可用在方法上
@Target(ElementType.METHOD)
//设置生命周期
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicDatasource {
    //默认为 learn
    String value() default "learn";
}
/**
 * @Author xuxu
 * @Date 2022/6/24
 * @Describe 测试动态数据源controller
 */
@RestController
public class TestDynamicDatasourceController {

    @Autowired
    private UserService userService;

    @GetMapping("getLearn")
    @DynamicDatasource
    public List<User> getLearn() {
        return userService.findAllUser();
    }

    @GetMapping("getLearnSlave")
    @DynamicDatasource("learnslave")
    public List<User> getLearnSlave(){
        return userService.findAllUser();
    }
}
5.自定义数据源扫描类并添加到AbstractRoutingDataSource的Map中(targetDataSources,resolvedDataSources)并在启动类上排除spring自身的数据源扫描类
//标注为配置类
@Configuration
public class MyDataSourceAutoConfiguration {

    private final Logger log = LoggerFactory.getLogger(MyDataSourceAutoConfiguration.class);

    //加入到IoC容器,如果没有key默认为该方法名 -> learnDataSource
    @Bean
    //此处能扫描到yml中的配置信息
    @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.learn")
    public DataSource learnDataSource() {
        log.info("创建 learn 数据源   ...");
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.learnslave")
    public DataSource learnSlaveDataSource() {
        log.info("创建 learnslave 数据源   ...");
        return DataSourceBuilder.create().build();
    }

    @Bean
    //@Primary 该类返回有多个 DataSource 需要指定一个默认的
    @Primary
    public DataSource masterDataSource(
            //@Qualifier -> 因IoC容器中有多个DataSource ,所以需要通过key再次指定
            @Autowired @Qualifier("learnDataSource") DataSource learnDataSource,
            @Autowired @Qualifier("learnSlaveDataSource") DataSource learnSlaveDataSource
    ) {
        Map<Object, Object> map = new HashMap<>();
        map.put("learn", learnDataSource);
        map.put("learnslave", learnSlaveDataSource);
        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.setTargetDataSources(map);
        return routingDataSource;
    }
}
//排除spring自身的数据源获取类
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
//配置mapper路径
@MapperScan({"com.ziye.mapper"})
public class DynamicDatasourceApplication {
    public static void main(String[] args) {
        SpringApplication.run(DynamicDatasourceApplication.class, args);
    }
}
6.通过源码得知获取连接前执行了determineTargetDataSource()方法,再从determineCurrentLookupKey()方法中,拿到了当前容器的key,通过key从Map中获取到了数据源.
此处由determineCurrentLookupKey()方法得知将数据源的key存储到ThreadLocal中
接下来创建自己的数据源获取类并继承AbstractRoutingDataSource并重写determineCurrentLookupKey()方法

动态切换USB descriptor_java_02

动态切换USB descriptor_mybatis_03

public class RoutingDataSourceContext {

    //存储到 ThreadLocal 中
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public RoutingDataSourceContext(String key) {
        threadLocal.set(key);
    }
	
	//方便取当前threadLocal中的key
    public static String getRoutingDataSourceKey() {
        String key = threadLocal.get();
        return key == null ? "learn" : key;
    }
}
供切面类调用
public class RoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        //从 threadLocal 中拿到数据源的key
        return RoutingDataSourceContext.getRoutingDataSourceKey();
    }
}
7.编写aop
//标注为是一个AOP切面类
@Aspect
//@Component 把类实例化到容器中,相当于配置文件中的 <bean id="" class=""/>
@Component
public class RoutingAspect {

    //@Around环绕通知 -> SpringAOP 增强注解
    //@Around("execution(* com.ziye.controller.*.*(..))") -> 执行controller包下任意方法时均会进行增强
    @Around("@annotation(dynamicDatasource)")
    //参数 1: ProceedingJoinPoint -> 正在执行的连接点
    //参数 2: 传入的实例注解
    public Object routingAspect(ProceedingJoinPoint point, DynamicDatasource dynamicDatasource) throws Throwable {
        // 拿到注解上的参数
        String key = dynamicDatasource.value();
        new RoutingDataSourceContext(key);
        //继续执行方法
        return point.proceed();
    }
}
8.测试

动态切换USB descriptor_spring_04


动态切换USB descriptor_java_05


动态切换USB descriptor_数据源_06

end