前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot动态数据源,还能玩出新花样,又学废了!

SpringBoot动态数据源,还能玩出新花样,又学废了!

作者头像
程序员小义
发布2024-09-24 18:36:14
660
发布2024-09-24 18:36:14
举报
文章被收录于专栏:小义思

多个数据源

大家好,我是小义。在SpringBoot应用开发中,不免会遇到配置多数据源的情况,也就是需要连接多个数据库,这时我们可以引入MyBatis Plus的dynamic-datasource动态数据源插件来解决。

但是现在有这样一个场景,系统需要切换新的数据源,但是为了避免上线后新数据源可能因不稳定出问题,需要可以通过开关灵活切回旧数据源,保证系统功能正常,又该怎么实现呢?其实可以借鉴dynamic-datasource的思想。

动态数据源

先来看看dynamic-datasource的实现原理。在引入maven依赖后,只需要在项目配置文件设置好参数,即可通过在方法上添加@DS注解来实现动态切换。

代码语言:javascript
复制
# 配置数据源
spring:
  datasource:
    # 配置动态数据源信息
    dynamic:
      # 默认主数据源
      primary: ds1
      # 配置数据源信息
      datasource:
        # 数据源名称,自己定义
        ds1:
          url: jdbc:mysql://localhost:3306/test_01?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8
          username: xxx
          password: xxx
          driver-class-name: com.mysql.cj.jdbc.Driver
        ds2:
          url: jdbc:mysql://localhost:3306/test_02?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8
          username: xxx
          password: xxx
          driver-class-name: com.mysql.cj.jdbc.Driver

@DS 注解的执行原理如下:

  1. 当方法执行时,如果该方法或其所在的类被@DS注解标记,AOP拦截器会获取注解中指定的数据源名称,并将获取到的数据源名称放入一个使用ThreadLocal管理的栈结构中,用于存储当前线程的数据源键值。这样,当前线程的所有数据库操作都会使用这个数据源。
  2. 在应用启动时,所有的数据源配置会被加载并存储在 DynamicRoutingDataSource 的 dataSourceMap 中,键为数据源名称,值为对应的 DataSource 实例。
  3. 方法执行完毕后,拦截器会从 DynamicDataSourceContextHolder 中移除当前线程的数据源键值,以确保不影响其他操作。

拦截器DynamicDataSourceAnnotationInterceptor核心代码:

代码语言:javascript
复制
public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {
    //...
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        String dsKey = determineDatasourceKey(invocation);
        DynamicDataSourceContextHolder.push(dsKey);
        try {
            return invocation.proceed();
        } finally {
            DynamicDataSourceContextHolder.poll();
        }
    }
    //...
}

灵活切换

由此不难看出,数据源切换,其实就是在一个Map维护所有数据源,然后在不同条件下取不同的值。借鉴这个思路,我们再回过头来解决系统上线安全切换数据源的问题。

数据源配置

首先在配置类中定义两个数据源bean。

代码语言:javascript
复制
@Log4j2
@Configuration
public class DatasourceConfig {

    @ConfigurationProperties(prefix = "spring.datasource.druid")
    @Bean(name = "ds1", initMethod = "init", destroyMethod = "close")
    public DataSource dataSource(DataSourceProperties dataSourceProperties) {
        DataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
        return datasource;    
    }
    
    @Bean(name = "ds2")
    public DataSource dataSource2() {
        //...
        return datasource;    
    }
}

动态数据源组件

借助org.springframework.jdbc.datasource.lookup.AbstrctRoutingDataSource,继承该类实现动态数据源的bean,并保存所有数据源,通过实现determineCurrentLookupKey()方法来指定具体的数据源。这里CONTEXT_HOLDER配置了apollo取值,这样就可以实现无需重启服务的开关效果了。

代码语言:javascript
复制
@Component
@Lazy
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Value("${spring.dynamicDataSource.current:ds2}")
    private String CONTEXT_HOLDER;

    public DynamicDataSource(@Autowired DataSource ds1, @Autowired DataSource ds2) {
        HashMap<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("ds1", ds1);
        targetDataSources.put("ds2", ds2);
        this.setDefaultTargetDataSource(shardingSphereDataSource);
        this.setTargetDataSources(targetDataSources);
    }

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

AbstrctRoutingDataSource

至于为啥determineCurrentLookupKey可以像threadlocal一样保证当前线程使用同一个数据源,是因为AbstrctRoutingDataSource在每次获取数据库连接时都会先调用该方法获取key,从而判断应该从Map里取哪个数据源。

代码语言:javascript
复制
//
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    //...
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        } else {
            return dataSource;
        }
    }
    //...

最后

至此,数据源配置已完成,以后遇到多数据源再也不怕啦。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-09-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员小义 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 多个数据源
  • 动态数据源
  • 灵活切换
    • 数据源配置
      • 动态数据源组件
        • AbstrctRoutingDataSource
        • 最后
        相关产品与服务
        对象存储
        对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档