1. 数据源准备
1.1 创建配置文件 application.yaml
spring:
  datasource:
    master:
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: 123456
      jdbc-url: jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&allowMultiQueries=true
    slave:
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: 123456
      jdbc-url: jdbc:mysql://localhost:3306/slave?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&allowMultiQueries=true 
1.2 数据库数据


存在两个库 master、slave,每个库中都有一个 route 表,每个表中有一条数据,其中 sign 为库名
2. 相关代码
2.1 创建实体类 DynamicDataSourceContextHolder
public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    public static void set(String dataSource) {
        CONTEXT_HOLDER.set(dataSource);
    }
    public static String get() {
        return CONTEXT_HOLDER.get();
    }
    public static void remove() {
        CONTEXT_HOLDER.remove();
    }
    
}
 
用于获取、设置、移除数据源
2.2 创建实体类 DynamicDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.get();
    }
} 
DynamicDataSource 继承自 AbstractRoutingDataSource,AbstractRoutingDataSource 的 getConnection 方法会通过调用 determineTargetDataSource 方法推断数据源
2.3 创建枚举类 DataSourceEnum
public enum DataSourceEnum {
    MASTER("master"),
    SLAVE("slave");
    private final String value;
    public String getValue() {
        return value;
    }
    DataSourceEnum(String value) {
        this.value = value;
    }
} 
2.4 创建配置类 DataSourceConfig
@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    @Bean
    @Primary
    public DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                               @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(DataSourceEnum.MASTER.getValue(), masterDataSource);
        dataSourceMap.put(DataSourceEnum.SLAVE.getValue(), slaveDataSource);
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }
} 
2.4.1 注意点
- masterDataSource()、slaveDataSource() 方法的返回值的类型是 HikariDataSource,HikariDataSource 只存在属性 jdbcUrl,不存在属性 url,所以配置文件 application.yaml 的相关属性要改成 jdbcUrl
 - dynamicDataSource() 方法需要添加 @Primary 注解,因为当前环境中存在3个类型为 DataSource 的 bean,这样可以让其他依赖 DataSource 的 bean 优先选择 DynamicDataSource,然后再通过 DynamicDataSource 的 determineCurrentLookupKey 方法决定实际使用的数据源
 
3. 自定义注解用于切换数据源
3.1 创建自定义注解 DataSourceRoute
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceRoute {
    DataSourceEnum value() default DataSourceEnum.MASTER;
} 
3.2 创建切面 DataSourceRouteAspect
@Component
@Aspect
public class DataSourceRouteAspect {
    @Pointcut("@annotation(com.ys.blog.annotation.DataSourceRoute)")
    public void pointcut() {
    }
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        Method method = pjp.getTarget().getClass().getMethod(ms.getName(), ms.getParameterTypes());
        if (method.isAnnotationPresent(DataSourceRoute.class)) {
            DataSourceRoute dataSourceRoute = method.getAnnotation(DataSourceRoute.class);
            DataSourceEnum dataSourceEnum = dataSourceRoute.value();
            DynamicDataSourceContextHolder.set(dataSourceEnum.getValue());
        }
        return pjp.proceed(pjp.getArgs());
    }
} 
4. 引入 mybatis
4.1 创建 RouteMapper.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.ys.blog.mapper.RouteMapper">
    <select id="getSign" resultType="java.lang.String">
        select sign from route where id = 1
    </select>
</mapper> 
4.2 创建接口 RouteMapper
public interface RouteMapper {
    String getSign();
} 
5. 其他
5.1 创建 RouteController
@RestController
@RequestMapping("/route")
public class RouteController {
    @Autowired
    private RouteMapper routeMapper;
    @GetMapping("/master")
    @DataSourceRoute(DataSourceEnum.MASTER)
    public String masterRoute() {
        return routeMapper.getSign();
    }
    @GetMapping("/slave")
    @DataSourceRoute(DataSourceEnum.SLAVE)
    public String slaveRoute() {
        return routeMapper.getSign();
    }
} 
5.2 创建启动类 BlogApplication
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableConfigurationProperties
@MapperScan(value = "com.ys.blog.mapper")
public class BlogApplication {
    public static void main(String[] args) {
        SpringApplication.run(BlogApplication.class, args);
    }
} 
5.2.1 注意点
注解 @SpringBootApplication 需要移除自动配置类 DataSourceAutoConfiguration,不然会导致循环依赖

5.2.2 原因简述
DataSourceAutoConfiguration 会导入一个类型为 DataSourceInitializerPostProcessor 的 BeanPostProcessor。如果 bean 的类型是 DataSource,会实例化一个类型为 DataSourceInitializerInvoker 的 bean,DataSourceInitializerInvoker 的构造方法依赖 DataSource


综上所述:DynamicDataSource 的实例化会触发 masterDataSource() 方法执行,masterDataSource() 方法的返回值类型是 DataSource,所以又会触发 DataSourceInitializerInvoker 的实例化,DataSourceInitializerInvoker 的实例化又依赖一个类型为 DataSource 的 bean,所以就产生了循环依赖
6. 测试
6.1 访问 master 数据源

通过运行结果,得出结论:访问的是 master 数据源
6.2 访问 slave 数据源

通过运行结果,得出结论:访问的是 slave 数据源



















