Bootstrap

SpringBoot实现【动态数据源配置】这一篇就够了

0. 排除数据源的自动装配

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

1. yml配置多个数据源信息

spring:
  datasource:
    master:
      driverClassName: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.124.200:3306/monitor
      username: root
      password: root
    salve:
      driverClassName: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.124.201:3306/monitor
      username: root
      password: root

2. 使用ThreadLocal,保存数据源名称供下游业务使用

public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    // 设置数据源名
    public static void setDataSource(String dbName) {
        contextHolder.set(dbName);
    }

    // 获取数据源名
    public static String getDataSource() {
        return (contextHolder.get());
    }

    // 清除数据源名
    public static void clearDataSource() {
        contextHolder.remove();
    }
}

3. 定义数据源类,继承AbstractRoutingDataSource

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }

}

4. 定义配置类读取数据源信息,设置数据源对象属性

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource master(){
        return DataSourceBuilder.create().build();
    }
    
    @Bean
    @ConfigurationProperties("spring.datasource.slave")
    public DataSource slave(){
        return DataSourceBuilder.create().build();
    }
    
    @Primary
    @Bean
    public DynamicDataSource dataSource(DataSource master, DataSource slave) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", master);
        targetDataSources.put("slave", slave);
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }
}

5. 定义自定义数据源注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DS {
    String value() default "master";
}

6. 定义切面,设置数据源名到ThreadLocal中

@Component
@Aspect
public class DynamicDataSourceAspect {

    @Pointcut("@annotation(com.iteng.annotation.DS)")
    public void dynamicDataSourcePointCut() {

    }

    @Around("dynamicDataSourcePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String dataSourceKey;
        // 类上的注解
        Class<?> aClass = joinPoint.getTarget().getClass();
        DS annotation = aClass.getAnnotation(DS.class);

        // 方法上的注解
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        DS annotationMethod = signature.getMethod().getAnnotation(DS.class);

        if (Objects.nonNull(annotationMethod)) {
            dataSourceKey = annotationMethod.value();
        } else {
            dataSourceKey = annotation.value();
        }
        // 设置数据源
        DataSourceContextHolder.setDataSource(dataSourceKey);
        try {
            return joinPoint.proceed();
        }finally {
            DataSourceContextHolder.clearDataSource();
        }
    }
}

7. 使用动态数据源

@DS(value = "salve")
@GetMapping("/get")
public R getName(){
    // 数据库操作
}

@DS
@PostMapping("/save")
public R sageName(){
    // 数据库操作
}

流程:

  • getName() 方法在执行之前,先执行对应的切面类,将“salve”这个数据源名称保存带ThreadLocal中
  • 然后执行getName() 方法,当遇到数据库操作时,首先要与数据库进行连接。
  • MyBatis 会先找 DataSource 的实现类,因为 DataSource 有getConnection() 方法。
  • 继承了 AbstractRoutingDataSource 的 DynamicDataSource 会被找到。
  • 然后执行 DynamicDataSource 中的 determineTargetDataSource 方法,即确定 AbstractRoutingDataSource 中 Map 对应的Key,然后获取对应的数据源对象。
  • 进行数据库连接。
;