SpringBoot 实现动态切换数据源

项目中,需要从不同的数据库中获取数据然后写入到当前数据库中,因此涉及到切换数据源问题。可以使用Mybatis-plus中提供的动态数据源SpringBoot的starter:dynamic-datasource-spring-boot-starter来实现。

如果没有引用Mybatis-plus的情况下,可以采用**ThreadLocal+AbstractRoutingDataSource**来模拟实现dynamic-datasource-spring-boot-starter中线程数据源切换。

1 简介

上述提到了ThreadLocal和AbstractRoutingDataSource,我们来对其进行简单介绍下。

ThreadLocal:想必大家必不会陌生,全称:thread local variable。主要是为解决多线程时由于并发而产生数据不一致问题。

ThreadLocal为每个线程提供变量副本,确保每个线程在某一时间访问到的不是同一个对象,这样做到了隔离性,增加了内存,但大大减少了线程同步时的性能消耗,减少了线程并发控制的复杂程度。

  • ThreadLocal作用: 在一个线程中共享,不同线程间隔离

  • ThreadLocal原理: ThreadLocal存入值时,会获取当前线程实例作为key,存入当前线程对象中的Map中。

AbstractRoutingDataSource:根据用户定义的规则选择当前的数据源。

作用:在执行查询之前,设置使用的数据源,实现动态路由的数据源,在每次数据库查询操作前执行它的抽象方法determineCurrentLookupKey(),决定使用哪个数据源。

2 代码实现

程序环境:

  • SpringBoot2.4.8

  • Mybatis-plus3.2.0

  • Druid1.2.6

  • lombok1.18.20

  • commons-lang3 3.10

2.1 实现ThreadLocal

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

java 复制代码
publicclass DataSourceContextHolder {
    //此类提供线程局部变量。这些变量不同于它们的正常对应关系是每个线程访问一个线程(通过get、set方法),有自己的独立初始化变量的副本。
    privatestaticfinal 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方法进行关联,实现动态切换数据源。

java 复制代码
/**
 * @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
java 复制代码
/**
 * @description: 设置数据源
 **/
@Configuration
publicclass 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());
        returnnew DynamicDataSource(defaultDataSource,dataSourceMap);
    }

}

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

2.4 优化调整
2.4.1 注解切换数据源

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

说到这有小伙伴应该就会想到使用注解来进行优化,接下来我们来实现一下。

2.4.1.1 定义注解

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

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

2.5.1.2 实现aop

复制代码
@Aspect
@Component
@Slf4j
publicclass 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不了解的小伙伴可以自行google或百度。

2.5.1.3 测试

添加两个测试方法:

java 复制代码
@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,因此在调用主数据源时,可以不用进行添加。

通过测试我们发现 可以调用到不同的数据源

好了,今天就跟大家唠叨到这,希望我的叨叨让大家对于动态切换数据源的方式能够有更深地理解。

相关推荐
周航宇JoeZhou2 小时前
JB2-7-HTML
java·前端·容器·html·h5·标签·表单
JZC_xiaozhong2 小时前
多系统权限标准不统一?企业如何实现跨平台统一权限管控
java·大数据·微服务·数据集成与应用集成·iam系统·权限治理·统一权限管理
我是Superman丶3 小时前
在 PostgreSQL 中使用 JSONB 类型并结合 MyBatis-Plus 实现自动注入,主要有以下几种方案
数据库·postgresql·mybatis
爬山算法3 小时前
Hibernate(85)如何在持续集成/持续部署(CI/CD)中使用Hibernate?
java·ci/cd·hibernate
Pluto_CSND3 小时前
基于mybatis-generator插件生成指定数据表的实体类、xml文件和dao层接口
mybatis
菜鸟233号3 小时前
力扣647 回文子串 java实现
java·数据结构·leetcode·动态规划
qq_12498707533 小时前
基于Java Web的城市花园小区维修管理系统的设计与实现(源码+论文+部署+安装)
java·开发语言·前端·spring boot·spring·毕业设计·计算机毕业设计
h7ml4 小时前
查券返利机器人的OCR识别集成:Java Tesseract+OpenCV优化图片验证码的自动解析方案
java·机器人·ocr
野犬寒鸦4 小时前
从零起步学习并发编程 || 第五章:悲观锁与乐观锁的思想与实现及实战应用与问题
java·服务器·数据库·学习·语言模型