多数据源切换

多数据源切换

HPC vvvvvvvip

数据源切换实现的3种方式

  1. 使用spring的AbstractRoutingDataSource抽象类
  2. 使用mybatis并重写SqlSessionFactory
  3. 使用dynamic-datasource-spring-boot-starter

使用AbstractRoutingDataSource抽象类

项目示例地址: https://github.com/StudyRecording/dynamicDataSource/tree/main/extendClass

通过继承AbstractRoutingDataSource抽象类并实现其中方法来更换数据员

1
2
3
4
5
6
7
// 返回数据源标识,
// 会在父类中的determineTargetDataSource方法中获取具体的数据源,然后进行数据源的链接
protected Object determineCurrentLookupKey();

// 在类进行初始化的时候执行,主要用来初始化类型的属性,
// 比如设置目标数据源,设置默认数据源等
public void afterPropertiesSet();

使用方式

  1. 通过代码直接切换
    1
    MultipleDataSource.name.set(DataSourceEnum.READ);
  2. 定义注解,通过切面进行数据源切换
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Component
    @Aspect
    public class DataSouceAop {

    /**
    * 方法的前置通知
    * @param point
    * @param ds
    */
    @Before("@annotation(ds)")
    public void before(JoinPoint point, DS ds) {
    DataSourceEnum value = ds.value();
    MultipleDataSource.name.set(value);
    }
    }
  3. 通过实现mybatis的插件进行数据源切换
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    @Intercepts({
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
    })
    public class DataSourcePlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    Object[] args = invocation.getArgs();
    MappedStatement ms = (MappedStatement) args[0];
    if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
    MultipleDataSource.name.set(DataSourceEnum.READ);
    } else {
    MultipleDataSource.name.set(DataSourceEnum.WRITE);
    }
    return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
    if (target instanceof Executor) {
    return Plugin.wrap(target, this);
    } else {
    return target;
    }
    }

    @Override
    public void setProperties(Properties properties) {
    Interceptor.super.setProperties(properties);
    }
    }

使用Mybatis并重写SqlSessionFactory实现

配置多个数据源就需要从写多个DataSourceTransactionManager、SqlSessionFactory、Mapper文件,且每个数据源与DataSourceTransactionManager、SqlSessionFactory和Mapper相互对应互相对应。

项目示例地址: https://github.com/StudyRecording/dynamicDataSource/tree/main/mybatisConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Configuration
@MapperScan(basePackages = "org.example.mapper.r", sqlSessionFactoryRef = "rSqlSessionFactory")
public class RMybatisConfig {

@Bean
@ConfigurationProperties(prefix = "spring.datasource.test")
public DataSource test() {
return DruidDataSourceBuilder.create().build();
}

@Bean
@Primary
public SqlSessionFactory rSqlSessionFactory() throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(test());
/*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/r/*.xml"));*/
return sessionFactory.getObject();
}

@Bean
public DataSourceTransactionManager rTransactionManager() {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(test());
return dataSourceTransactionManager;
}

@Bean
public TransactionTemplate rTransactionTemplate() {
return new TransactionTemplate(rTransactionManager());
}
}

使用方式

  1. 每个数据源利用Callable类声明相关的事务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Transactional(DbTxConstants.DB2_TX)
    public <V> V inTransaction(Callable<V> callable) {
    try {
    return callable.call();
    } catch (RuntimeException e) {
    throw e;
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    }
  2. 定义组合事务的类, 里面针对每个事务进行循环处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public <V> V inCombinedTx(Callable<V> callable, String[] transactions) {
    if (callable == null) {
    return null;
    }
    // 相当于循环 [RTransactionManager,wTransactionManager]
    Callable<V> combined = Stream.of(transactions)
    .filter(ele -> !StringUtils.isEmpty(ele))
    .distinct()
    .reduce(callable, (r, tx) -> {
    switch (tx) {
    case DbTxConstants.DB1_TX:
    return () -> db1TxBroker.inTransaction(r);
    case DbTxConstants.DB2_TX:
    return () -> db2TxBroker.inTransaction(r);
    default:
    return null;
    }
    }, (r1, r2) -> r2);

    try {
    return combined.call();
    } catch (RuntimeException e) {
    throw e;
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    }
  3. 声明注解,在aop中判断注解并使用组合事务类进行处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Around("@annotation(multiTransactional)")
    public Object inMultiTransactions(ProceedingJoinPoint pjp, MultiTransactional multiTransactional) {
    return comboTransaction.inCombinedTx(() -> {
    try {
    return pjp.proceed(); //执行目标方法
    } catch (Throwable throwable) {
    if (throwable instanceof RuntimeException) {
    throw (RuntimeException) throwable;
    }
    throw new RuntimeException(throwable);
    }
    }, multiTransactional.value());
    }

使用dynamic-datasource-spring-boot-starter

  1. 引用pom文件
  2. 使用@DS注解
    官方文档: https://baomidou.com/pages/a61e1b/#%E6%96%87%E6%A1%A3-documentation

项目示例: https://github.com/StudyRecording/dynamicDataSource/tree/main/dynamicDataSourceMybatisPlus

  • 标题: 多数据源切换
  • 作者: HPC
  • 创建于 : 2022-10-17 19:12:25
  • 更新于 : 2025-01-18 03:32:39
  • 链接: https://studyrecording.github.io/waste-code/2022/10/17/多数据源切换/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论