Loading

Mybatis多数据源(二)使用AbstractRoutingDataSource实现动态数据源切换

一、原理

Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行数据库操作之前,设置使用的数据源,

即可实现数据源的动态路由。它的抽象方法determineCurrentLookupKey() 决定使用哪个数据源。image

image

 

二、具体实现

具体的业务代码不贴了,就放一下构造动态数据源的部分

1、DynamicDataSource继承AbstractRoutingDataSource,重写determineCurrentLookupKey方法

/**动态数据源
 * 扩展 Spring 的 AbstractRoutingDataSource 抽象类,重写 determineCurrentLookupKey 方法
 * determineCurrentLookupKey() 方法决定使用哪个数据源
 *
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。
     * 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好
     *
     * @param defaultTargetDataSource 默认数据源
     * @param targetDataSources       目标数据源
     */
    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) {
        CONTEXT_HOLDER.set(dataSource);
    }

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

    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }

}

 

2、将动态数据源注入到Spring容器中

/**
 * 配置多数据源
 */
@Configuration
public class DynamicDataSourceConfig {

    private static final String FIRST = "first";

    private static final String SECOND = "second";

    @Bean
    @ConfigurationProperties("spring.datasource.druid.first")
    public DataSource firstDataSource(){

        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.second")
    public DataSource secondDataSource(){

        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>(5);
        targetDataSources.put(FIRST, firstDataSource);
        targetDataSources.put(SECOND, secondDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }

}

 

3、注解 + aop,指定本次数据库操作使用的数据源

/**
 * 多数据源注解
 * 指定要使用的数据源
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurDataSource {

    String name() default "";

}


/**
 * 多数据源,切面处理类
 *
 * @author xiaohe
 * @version V1.0.0
 */
@Slf4j
@Aspect
@Component
public class DataSourceAspect implements Ordered {

    private static final String FIRST = "first";

    private static final String SECOND = "second";

    @Pointcut("@annotation(com.hc.datasource.CurDataSource)")
    public void dataSourcePointCut() {

    }

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        CurDataSource ds = method.getAnnotation(CurDataSource.class);
        if (ds == null) {
            DynamicDataSource.setDataSource(FIRST);
            log.debug("set datasource is " + FIRST);
        } else {
            DynamicDataSource.setDataSource(ds.name());
            log.debug("set datasource is " + ds.name());
        }

        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
            log.debug("clean datasource");
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

4、yml文件

spring:
  application:
    name: test
  datasource:
    druid:
      type: com.alibaba.druid.pool.DruidDataSource
      driverClassName: com.mysql.jdbc.Driver
      first:
        url: jdbc:mysql://xxxx
        username: root
        password: root
      second:
        url: jdbc:mysql://xxx
        username: root
        password: root
mybatis:
  mapper-locations: classpath:mapper/*.xml

 

三、测试

service在调用mapper方法时,需要通过注解指定本次操作使用的数据源

@Service
public class TestService {

    @Autowired
    TestMapper testMapper;

    @CurDataSource(name = "first")
    public List<CategoryEntity> list() {
        List<CategoryEntity> list = testMapper.list();
        return list;
    }

    @CurDataSource(name = "second")
    public List<OrderEntity> orders() {
        List<OrderEntity> list = testMapper.orders();
        return list;
    }
}

 

访问 http://localhost:8080/categorys

image

查看日志

image

表明本次请求确实发生了数据源的切换

 

四、缺点

数据源的实例化做的不太好,本次就是有几个数据源,就手动实例化几个数据源。如果数据源很多的话,一个个构造的话很麻烦

下篇文章批量构造多个数据源

 

地址:https://gitee.com/houchen1996/abstract-routing-data-source-test

posted @ 2022-05-07 20:53  青岑  阅读(696)  评论(0编辑  收藏  举报