1. 程式人生 > 其它 >Mybatis多資料來源(二)使用AbstractRoutingDataSource實現動態資料來源切換

Mybatis多資料來源(二)使用AbstractRoutingDataSource實現動態資料來源切換

一、原理

Spring boot提供了AbstractRoutingDataSource 根據使用者定義的規則選擇當前的資料來源,這樣我們可以在執行資料庫操作之前,設定使用的資料來源,

即可實現資料來源的動態路由。它的抽象方法determineCurrentLookupKey() 決定使用哪個資料來源。

二、具體實現

具體的業務程式碼不貼了,就放一下構造動態資料來源的部分

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

檢視日誌

表明本次請求確實發生了資料來源的切換

四、缺點

資料來源的例項化做的不太好,本次就是有幾個資料來源,就手動例項化幾個資料來源。如果資料來源很多的話,一個個構造的話很麻煩

下篇文章批量構造多個數據源