# 多数据源切换

我们在项目中,有时候会碰到多个数据源切换的场景,这里从零开始说一下步骤!!也是做一个笔记备忘。

# 准备工作

事先准备好一个能走通curd的 springboot+mybatis的web项目

# 项目结构

4JNL8J.png

# 主从数据库

4JU0G4.png

# 保证一个数据库下接口访问正常

4JdUCF.png

# 多数据源切换

我们目前已经有一个基础项目,接下来正式开始进入正题。所谓多数据源切换,无非就是对Druid,HikriCP或者其他数据源上做手脚

废话不多说,先把我们的数据库连接字符串配上去

  1. 配置主从数据库连接 application.yml
spring:
  datasource:
    druid:
      biubiu-master: #主库
        url: jdbc:mysql://localhost:3306/biubiu?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: root
      biubiu-slave: #从库
        url: jdbc:mysql://localhost:3306/biubiu_slave?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: root

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  1. 把配置读到Java里,初始化数据源
  • DataSourceName 数据源名称
public class DataSourceName {

    private DataSourceName(){}

    public static final String MASTER = "biubiu";
    public static final String SLAVE = "biubiu_slave";

}
1
2
3
4
5
6
7
8
  • DynamicDataSource 动态数据源基类
public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

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

    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  • DynamicDataSourceConfig 多数据源配置类
@Configuration
public class DynamicDataSourceConfig {

  @Bean
  @ConfigurationProperties("spring.datasource.druid.biubiu-master")
  public DataSource masterDataSource() throws SQLException {
    return DruidDataSourceBuilder.create().build();
  }

  @Bean
  @ConfigurationProperties("spring.datasource.druid.biubiu-slave")
  public DataSource slaveDataSource() {
    return DruidDataSourceBuilder.create().build();
  }

  @Bean
  @Primary
  public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource) {
    Map<Object, Object> targetDataSources = new HashMap<>();
    targetDataSources.put(DataSourceName.MASTER, masterDataSource);
    targetDataSources.put(DataSourceName.SLAVE, slaveDataSource);
    return new DynamicDataSource(masterDataSource, targetDataSources);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

到此我们数据源配置,初始化完成!!接下来,开始做切换,我们采用Aop方式做数据源切换,大致思路是 Aop拦截注解,通过注解指定的数据源做切换

  1. 自定义注解 AOP拦截
  • DataSource注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "";
}
1
2
3
4
5
6
  • DataSourceAspect Aop拦截
@Aspect
@Component
public class DataSourceAspect {
    private static final Logger log = LoggerFactory.getLogger(DataSourceAspect.class);

    @Pointcut("@annotation(com.biubiu.multidatabase.config.DataSource) " + // 方法上的注解
             "|| @within(com.biubiu.multidatabase.config.DataSource)") // 类上的注解
    public void dataSourcePointCut() {
    }

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        //获取切入点的方法和对应的类
        MethodSignature signature = (MethodSignature) point.getSignature();
        Class targetClass = point.getTarget().getClass();
        Method method = signature.getMethod();

        //获取类和方法上的注解
        DataSource targetDataSource = (DataSource) targetClass.getAnnotation(DataSource.class);
        DataSource methodDataSource = method.getAnnotation(DataSource.class);
        
        if (targetDataSource != null || methodDataSource != null) {
            //获取类和方法上的注解的值方法优先于类注解
            String value;
            if (methodDataSource != null) {
                value = methodDataSource.value();
            } else {
                value = targetDataSource.value();
            }
             
            DynamicDataSource.setDataSource(value);
            log.debug("set datasource is {}", value);
        }

        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
            log.debug("clean datasource");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
  1. 开始使用 在Controller/Service/Mapper里可以都可以试试
    @RequestMapping("/loadMaster")
    @DataSource(name = DataSourceName.MASTER)
    public Object loadMaster(int id) {
            return userService.load(id);
    }

    @RequestMapping("/loadSlave")
    @DataSource(name = DataSourceName.SLAVE)
    public Object loadSlave(int id) {
            return userService.load(id);
    }
1
2
3
4
5
6
7
8
9
10
11

# 结论

4JBijJ.md.png

4JBPc4.md.png

# 源代码

本文对应代码 (opens new window)