SpringBoot实现动态切换数据源(含源码)

点击下载《SpringBoot实现动态切换数据源(含源码)》

1. 简介

ThreadLocalAbstractRoutingDataSource 是两个在Java中用于实现动态数据源切换的工具。

1.1 ThreadLocal:

ThreadLocal 是 Java 中的一个类,用于存储线程局部变量。线程局部变量与普通的变量不同,它不是共享的,每个线程都有其自己的独立的线程局部变量副本。这使得我们可以在多线程环境中为每个线程提供独立的变量副本,从而实现线程间的数据隔离。

在数据源切换的场景中,我们通常将数据源信息存储在 ThreadLocal 中,然后在数据访问层(如 DAO)中通过 ThreadLocal 来获取当前线程的数据源信息,从而动态地切换数据源。

1.2 AbstractRoutingDataSource:

AbstractRoutingDataSource 是 MyBatis-Plus 提供的一个数据源路由类。它可以基于某个条件来动态地切换数据源。当调用 selectAnyDataSources() 方法时,它会根据当前线程的上下文信息来返回一个数据源。如果没有设置上下文信息,它会返回默认的数据源。

结合 ThreadLocalAbstractRoutingDataSource,我们可以轻松地在运行时动态地切换数据源。基本步骤如下:

  1. 在业务代码中,根据业务逻辑和条件设置 ThreadLocal 中的数据源信息。例如:
DataSourceContextHolder.setDataSource("slave"); // 设置当前线程的数据源为 slave
  1. 在数据访问层(如 DAO),使用 AbstractRoutingDataSource 来获取数据源并执行查询。MyBatis-Plus 会根据 ThreadLocal 中的数据源信息来决定从哪个数据源中获取数据。

  2. 当业务逻辑执行完成后,记得清除 ThreadLocal 中的数据源信息,以避免对其他线程造成影响。

2. 代码实现

2.1 实现ThreadLocal

创建一个类用于实现ThreadLocal,主要是通过get,set,remove方法来获取、设置、删除当前线程对应的数据源。

/**
 * @description:
 **/
public class DataSourceContextHolder {
    //此类提供线程局部变量。这些变量不同于它们的正常对应关系是每个线程访问一个线程(通过get、set方法),有自己的独立初始化变量的副本。
    private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();

    /**
     * 设置数据源
     * @param dataSourceName 数据源名称
     */
    public static void setDataSource(String dataSourceName){
        DATASOURCE_HOLDER.set(dataSourceName);
    }

    /**
     * 获取当前线程的数据源
     * @return 数据源名称
     */
    public static String getDataSource(){
        return DATASOURCE_HOLDER.get();
    }

    /**
     * 删除当前数据源
     */
    public static void removeDataSource(){
        DATASOURCE_HOLDER.remove();
    }

}

2.2 实现AbstractRoutingDataSource

定义一个动态数据源类实现AbstractRoutingDataSource,通过determineCurrentLookupKey方法与上述实现的ThreadLocal类中的get方法进行关联,实现动态切换数据源。

/**
 * @description: 实现动态数据源,根据AbstractRoutingDataSource路由到不同数据源中
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {

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

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

上述代码中,还实现了一个动态数据源类的构造方法,主要是为了设置默认数据源,以及以Map保存的各种目标数据源。其中Map的key是设置的数据源名称,value则是对应的数据源(DataSource)。

2.3 配置数据库

application.yml中配置数据库信息:

#设置数据源
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      master:
        url: jdbc:mysql://xxxxxx:3306/test1?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      slave:
        url: jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      initial-size: 15
      min-idle: 15
      max-active: 200
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: ""
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: false
      connection-properties: false
/**
 * @description: 设置数据源
 **/
@Configuration
public class DateSourceConfig {

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

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

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource createDynamicDataSource(){
        Map<Object,Object> dataSourceMap = new HashMap<>();
        DataSource defaultDataSource = masterDataSource();
        dataSourceMap.put("master",defaultDataSource);
        dataSourceMap.put("slave",slaveDataSource());
        return new DynamicDataSource(defaultDataSource,dataSourceMap);
    }

}

通过配置类,将配置文件中的配置的数据库信息转换成datasource,并添加到DynamicDataSource中,同时通过@Bean将DynamicDataSource注入Spring中进行管理,后期在进行动态数据源添加时,会用到。

2.4 优化调整

在上述中,虽然已经实现了动态切换数据源,但是我们会发现如果涉及到多个业务进行切换数据源的话,我们就需要在每一个实现类中添加这一段代码。

2.4.1 定义注解

我们就用mybatis动态数据源切换的注解:DS,代码如下:

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

2.4.2 实现aop

@Aspect
@Component
@Slf4j
public class DSAspect {

    @Pointcut("@annotation(com.jiashn.dynamic_datasource.dynamic.aop.DS)")
    public void dynamicDataSource(){}

    @Around("dynamicDataSource()")
    public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature)point.getSignature();
        Method method = signature.getMethod();
        DS ds = method.getAnnotation(DS.class);
        if (Objects.nonNull(ds)){
            DataSourceContextHolder.setDataSource(ds.value());
        }
        try {
            return point.proceed();
        } finally {
            DataSourceContextHolder.removeDataSource();
        }
    }
}

代码使用了@Around,通过ProceedingJoinPoint获取注解信息,拿到注解传递值,然后设置当前线程的数据源。对aop不了解的小伙伴可以自行百度。

2.4.3 测试

添加两个测试方法:

@GetMapping("/getMasterData.do")
public String getMasterData(){
    TestUser testUser = testUserMapper.selectOne(null);
    return testUser.getUserName();
}

@GetMapping("/getSlaveData.do")
@DS("slave")
public String getSlaveData(){
    TestUser testUser = testUserMapper.selectOne(null);
    return testUser.getUserName();
}

由于@DS中设置的默认值是:master,因此在调用主数据源时,可以不用进行添加。我们通过@DS也进行了数据源的切换,实现了Mybatis-plus动态切换数据源中的通过注解切换数据源的方式。

点击下载《SpringBoot实现动态切换数据源(含源码)》

你可能感兴趣的:(知识库,spring,boot,java,后端)