一种数据源切换的实践方案

随着业务的不断深入,我们会碰见很多关于数据源切换的业务场景,数据源切换也是当前最常用的分库后的分流策略方式之一,对于读写职责分离的数据库集群而言,我们在服务层面制定相应的接口与数据库交互的定制化开发,也就是我们可以自己通过程序控制当前的接口访问的数据库的地址与数据源的实例对象。对于同一个业务系统使用到不同服务器的数据库而言,我们也可以动态的切换数据源,从而做到预先准备,用时切换的功能。当然数据源切换也只是一种解决方案,并不是最合适的实践思路,本文旨在与大家分享常用的数据源切换的实践思路。

AbstractRoutingDataSource核心抽象类

字面意思上,抽象路由数据源,其中的源码如下:

核心属性与实现的核心拓展接口InitializingBean(属性填充之前实例化之前)

 核心的某些设置目标数据源方法(透传给容器,或感知到容器的某些特性)

 下面就是重写Initializing接口的实现方法afterProptriesSet()属性填充之后的bean的自定义逻辑

 核心的切换数据源的方法,以及可以从中看出如何获取数据源的方法

 那么我们看完源码大概能了解到determineCurrentLookupKey方法就是我们核心的修改数据源的方法,只要我们在这个方法的实现里稍微修改一下KEY的返回逻辑,是不是就能够起到切换数据源的逻辑?

由此我们设计一个自定义的动态数据源类:

@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDynamicDataSourceKey();
    }
}

这里的DynamicDataSourceHolder类主要的作用是利用ThreadLocal<String>维护一个请求自己的数据源信息:

@Slf4j
public class DynamicDataSourceHolder {

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

    public static void setDynamicDataSourceKey(String key){
        log.info("数据源切换为:{}",key);
        DYNAMIC_DATASOURCE_KEY.set(key);
    }

    public static String getDynamicDataSourceKey(){
        String key = DYNAMIC_DATASOURCE_KEY.get();
        return key == null ? DataSourceType.WRITE: key;
    }

    public static void removeDynamicDataSourceKey(){
        log.info("移除数据源:{}",DYNAMIC_DATASOURCE_KEY.get());
        DYNAMIC_DATASOURCE_KEY.remove();
    }
}

那么切换的逻辑已经基本都交给容器去处理了,我们接下来要实现读取配置文件中的多数据源设置参数,并将这些数据源实例化后交给容器管理。

由此我们设计一个DruidDataSourceConfiguration:

@Configuration
@ConditionalOnClass(DruidDataSource.class)
@ConditionalOnProperty(
        name = "spring.datasource.druid.write",
        matchIfMissing = true
)
public class DataSourceConfiguration {
    @Bean(name= DataSourceType.WRITE)
    @Qualifier(DataSourceType.WRITE)
    @ConfigurationProperties("spring.datasource.druid.write")
    public DruidDataSource wirteDruidDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name= DataSourceType.READ)
    @Qualifier(DataSourceType.READ)
    @ConfigurationProperties("spring.datasource.druid.read")
    public DruidDataSource readDruidDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DynamicDataSource dynamicDataSource()
    {
        Map<Object, Object> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put(DataSourceType.WRITE,wirteDruidDataSource());
        dataSourceMap.put(DataSourceType.READ,readDruidDataSource());
        //设置动态数据源
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(wirteDruidDataSource());
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }
}

这里主要的调用也是在之前的源码里有提到,我们还定义了一个所谓的接口:

public interface DataSourceType{

    String WRITE = "WRITE";
    String READ = "READ";

}

接下来我们还要利用注解规定某一个接口是否需要切换数据源:

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    /*数据源类型*/
    String value() default DataSourceType.READ;
}

通常最常用的方式就是aop去代理接口所属的对象,拿到代理的数据信息进行处理,这里我们是不是可以切换当前数据源到我们注解上过规定的数据源呢?

@Aspect
@Slf4j
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.runjing.learn_runjing.dataSource.DataSource)")
    public void point(){}

    @Around("point()")
    public void setDataSource(ProceedingJoinPoint pointcut) throws Throwable{
        String value = getDefineAnnotation(pointcut).value();
        DynamicDataSourceHolder.setDynamicDataSourceKey(value);
        try{
            pointcut.proceed();
        }finally {
            DynamicDataSourceHolder.removeDynamicDataSourceKey();
        }
    }

    private DataSource getDefineAnnotation(ProceedingJoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        DataSource dataSourceAnnotation = methodSignature.getMethod().getAnnotation(DataSource.class);
        if (Objects.nonNull(methodSignature)) {
            return dataSourceAnnotation;
        } else {
            Class<?> dsClass = joinPoint.getTarget().getClass();
            return dsClass.getAnnotation(DataSource.class);
        }
    }

}

以上,一种动态数据源的切换实践方案分享结束。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ForestSpringH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值