SpringBoot多数据源切换详解,以及开启事务后数据源切换失败处理

  • Post author:
  • Post category:其他



SpringBoot多数据源切换详解,以及开启事务后数据源切换失败处理_zzhongcy的博客-CSDN博客_同一个事务切多次数据源



附:糖豆广场舞永久会员TV版

最近项目需要指出多数据源,同时支持事务回滚,这里记录一下

1、多数据源方式介绍

主要方式有以下两种:

通过配置多个SqlSessionFactory 来实现多数据源,这么做的话,未免过于笨重,而且无法实现动态添加数据源这个需求


通过 spring AbstractRoutingDataSource 为我们抽象了一个 DynamicDataSource 解决这一问题

2、多数据源实现

2.1、分包方式实现:

2.1.1、在application.properties中配置两个数据库:

## test1 database

spring.datasource.test1.url=jdbc:mysql://localhost:3307/multipledatasource1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false

spring.datasource.test1.username=root

spring.datasource.test1.password=root

spring.datasource.test1.driver-class-name=com.mysql.cj.jdbc.Driver

## test2 database

spring.datasource.test2.url=jdbc:mysql://localhost:3307/multipledatasource2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false

spring.datasource.test2.username=root

spring.datasource.test2.password=root

spring.datasource.test2.driver-class-name=com.mysql.cj.jdbc.Driver

2.1.2、建立连个数据源的配置文件:

springbooot中的参数可以参考上一篇博客(不定期更新中):https://blog.csdn.net/tuesdayma/article/details/81029539

第一个配置文件:

//表示这个类为一个配置类

@Configuration

// 配置mybatis的接口类放的地方

@MapperScan(basePackages = “com.mzd.multipledatasources.mapper.test01”, sqlSessionFactoryRef = “test1SqlSessionFactory”)

public class DataSourceConfig1 {


// 将这个对象放入Spring容器中

@Bean(name = “test1DataSource”)

// 表示这个数据源是默认数据源

@Primary

// 读取application.properties中的配置参数映射成为一个对象

// prefix表示参数的前缀

@ConfigurationProperties(prefix = “spring.datasource.test1”)

public DataSource getDateSource1() {


return DataSourceBuilder.create().build();

}

@Bean(name = “test1SqlSessionFactory”)

// 表示这个数据源是默认数据源

@Primary

// @Qualifier表示查找Spring容器中名字为test1DataSource的对象

public SqlSessionFactory test1SqlSessionFactory(@Qualifier(“test1DataSource”) DataSource datasource)

throws Exception {


SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

bean.setDataSource(datasource);

bean.setMapperLocations(

// 设置mybatis的xml所在位置

new PathMatchingResourcePatternResolver().getResources(“classpath*:mapping/test01/*.xml”));

return bean.getObject();

}

@Bean(“test1SqlSessionTemplate”)

// 表示这个数据源是默认数据源

@Primary

public SqlSessionTemplate test1sqlsessiontemplate(

@Qualifier(“test1SqlSessionFactory”) SqlSessionFactory sessionfactory) {


return new SqlSessionTemplate(sessionfactory);

}

}

第二个配置文件:

@Configuration

@MapperScan(basePackages = “com.mzd.multipledatasources.mapper.test02”, sqlSessionFactoryRef = “test2SqlSessionFactory”)

public class DataSourceConfig2 {


@Bean(name = “test2DataSource”)

@ConfigurationProperties(prefix = “spring.datasource.test2”)

public DataSource getDateSource2() {


return DataSourceBuilder.create().build();

}

@Bean(name = “test2SqlSessionFactory”)

public SqlSessionFactory test2SqlSessionFactory(@Qualifier(“test2DataSource”) DataSource datasource)

throws Exception {


SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

bean.setDataSource(datasource);

bean.setMapperLocations(

new PathMatchingResourcePatternResolver().getResources(“classpath*:mapping/test02/*.xml”));

return bean.getObject();

}

@Bean(“test2SqlSessionTemplate”)

public SqlSessionTemplate test2sqlsessiontemplate(

@Qualifier(“test2SqlSessionFactory”) SqlSessionFactory sessionfactory) {


return new SqlSessionTemplate(sessionfactory);

}

}

注意:

1、@Primary这个注解必须要加,因为不加的话spring将分不清楚那个为主数据源(默认数据源)

2、mapper的接口、xml形式以及dao层都需要两个分开,目录如图:

3、bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(“XXXX”));mapper的xml形式文件位置必须要配置,不然将报错:no statement (这种错误也可能是mapper的xml中,namespace与项目的路径不一致导致的,具体看情况吧,注意一下就行,问题不大的)

4、在service层中根据不同的业务注入不同的dao层。

5、如果是主从复制- -读写分离:比如test01中负责增删改,test02中负责查询。但是需要注意的是负责增删改的数据库必须是主库(master)

6、如果是分布式结构的话,不同模块操作各自的数据库就好,test01包下全是test01业务,test02全是test02业务,但是如果test01中掺杂着test02的编辑操作,这时候将会产生事务问题:即test01中的事务是没法控制test02的事务的,这个问题在之后的博客中会解决。

2.2 AOP实现:

简介: 用这种方式实现多数据源的前提必须要清楚两个知识点:AOP原理和AbstractRoutingDataSource抽象类。

1、AOP:这个东西。。。不切当的说就是相当于拦截器,只要满足要求的都会被拦截过来,然后进行一些列的操作。具体需要自己去体会。。。

2、AbstractRoutingDataSource:这个类是实现多数据源的关键,他的作用就是动态切换数据源,实质:有多少个数据源就存多少个数据源在targetDataSources(是AbstractRoutingDataSource的一个map类型的属性,其中value为每个数据源,key表示每个数据源的名字)这个属性中,然后根据determineCurrentLookupKey()这个方法获取当前数据源在map中的key值,然后determineTargetDataSource()方法中动态获取当前数据源,如果当前数据源不存并且默认数据源也不存在就抛出异常。

1、创建枚举类DataSourceKey列出你所有的数据源名称,当然了,类名你可以按照自己的取名习惯,下面所有的类也是如此。

public enum DataSourceKey {


DB_MASTER,

DB_SLAVE1,

DB_SLAVE2,

DB_OTHER

}

2、创建DynamicDataSourceContextHolder类,这个类是为了解决多线程访问全局变量的问题。

import org.apache.commons.lang3.RandomUtils;

import org.apache.log4j.Logger;

/**

* @author RocLiu [apedad@qq.com]

* @version 1.0

*/

public class DynamicDataSourceContextHolder {


private static final Logger LOG = Logger.getLogger(DynamicDataSourceContextHolder.class);

private static final ThreadLocal<DataSourceKey> currentDatesource = new ThreadLocal<>();

/**

* 清除当前数据源

*/

public static void clear() {


currentDatesource.remove();

}

/**

* 获取当前使用的数据源

*

* @return 当前使用数据源的ID

*/

public static DataSourceKey get() {


return currentDatesource.get();

}

/**

* 设置当前使用的数据源

*

* @param value 需要设置的数据源ID

*/

public static void set(DataSourceKey value) {


currentDatesource.set(value);

}

/**

* 设置从从库读取数据

* 采用简单生成随机数的方式切换不同的从库

*/

public static void setSlave() {


if (RandomUtils.nextInt(0, 2) > 0) {


DynamicDataSourceContextHolder.set(DataSourceKey.DB_SLAVE2);

} else {


DynamicDataSourceContextHolder.set(DataSourceKey.DB_SLAVE1);

}

}

}

3、创建类DynamicRoutingDataSource继承AbstractRoutingDataSource类并且实现determineCurrentLookupKey()方法,设置数据源。

import org.apache.log4j.Logger;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {


private static final Logger LOG = Logger.getLogger(DynamicRoutingDataSource.class);

@Override

protected Object determineCurrentLookupKey() {


LOG.info(“当前数据源:{}”+ DynamicDataSourceContextHolder.get());

return DynamicDataSourceContextHolder.get();

}

}

4、配置数据源,这一步比较重要,创建配置类DynamicDataSourceConfiguration

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;

import com.apedad.example.commons.DataSourceKey;

import com.apedad.example.commons.DynamicRoutingDataSource;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.mybatis.spring.SqlSessionTemplate;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

import java.util.HashMap;

import java.util.Map;

@MapperScan(basePackages = “com.apedad.example.dao”)

@Configuration

public class DynamicDataSourceConfiguration {


@Bean

@ConfigurationProperties(prefix = “multiple.datasource.master”)//此处的”multiple.datasource.master”需要你在application.properties中配置,详细信息看下面贴出的application.properties文件。

public DataSource dbMaster() {


return DruidDataSourceBuilder.create().build();

}

@Bean

@ConfigurationProperties(prefix = “multiple.datasource.slave1”)

public DataSource dbSlave1() {


return DruidDataSourceBuilder.create().build();

}

@Bean

@ConfigurationProperties(prefix = “multiple.datasource.slave2”)

public DataSource dbSlave2() {


return DruidDataSourceBuilder.create().build();

}

@Bean

@ConfigurationProperties(prefix = “multiple.datasource.other”)

public DataSource dbOther() {


return DruidDataSourceBuilder.create().build();

}

/**

* 核心动态数据源

*

* @return 数据源实例

*/

@Bean

public DataSource dynamicDataSource() {


DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();

dataSource.setDefaultTargetDataSource(dbMaster());

Map<Object, Object> dataSourceMap = new HashMap<>(4);

dataSourceMap.put(DataSourceKey.DB_MASTER, dbMaster());

dataSourceMap.put(DataSourceKey.DB_SLAVE1, dbSlave1());

dataSourceMap.put(DataSourceKey.DB_SLAVE2, dbSlave2());

dataSourceMap.put(DataSourceKey.DB_OTHER, dbOther());

dataSource.setTargetDataSources(dataSourceMap);

return dataSource;

}

@Bean

public SqlSessionFactory sqlSessionFactory() throws Exception {


SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

sqlSessionFactoryBean.setDataSource(dynamicDataSource());

//此处设置为了解决找不到mapper文件的问题

sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(“classpath:mapper/*.xml”));

return sqlSessionFactoryBean.getObject();

}

@Bean

public SqlSessionTemplate sqlSessionTemplate() throws Exception {


return new SqlSessionTemplate(sqlSessionFactory());

}

/**

* 事务管理

*

* @return 事务管理实例

*/

@Bean

public PlatformTransactionManager platformTransactionManager() {


return new DataSourceTransactionManager(dynamicDataSource());

}

}

5、为了不影响业务代码而实现数据源切换,我决定使用AOP切换数据源,为了准确的知道哪个地方需要切换哪个数据源,我这里使用自定义注解的方式,如果你又更好的方式也推荐你使用自己的方式。创建自定义注解类:TargetDataSource:

import com.apedad.example.commons.DataSourceKey;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface TargetDataSource {


DataSourceKey dataSourceKey() default DataSourceKey.DB_MASTER;

}

6、编写数据源切换切面类:DynamicDataSourceAspect

import com.apedad.example.annotation.TargetDataSource;

import org.apache.log4j.Logger;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

import org.aspectj.lang.reflect.MethodSignature;

import org.springframework.core.annotation.Order;

import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect

@Order(-1)

@Component

public class DynamicDataSourceAspect {


private static final Logger LOG = Logger.getLogger(DynamicDataSourceAspect.class);

@Pointcut(“execution(* com.apedad.example.service.*.list*(..))”)

public void pointCut() {


}

/**

* 执行方法前更换数据源

*

* @param joinPoint        切点

* @param targetDataSource 动态数据源

*/

@Before(“@annotation(targetDataSource)”)

public void doBefore(JoinPoint joinPoint, TargetDataSource targetDataSource) {


DataSourceKey dataSourceKey = targetDataSource.dataSourceKey();

if (dataSourceKey == DataSourceKey.DB_OTHER) {


LOG.info(String.format(“设置数据源为  %s”, DataSourceKey.DB_OTHER));

DynamicDataSourceContextHolder.set(DataSourceKey.DB_OTHER);

} else {


LOG.info(String.format(“使用默认数据源  %s”, DataSourceKey.DB_MASTER));

DynamicDataSourceContextHolder.set(DataSourceKey.DB_MASTER);

}

}

/**

* 执行方法后清除数据源设置

*

* @param joinPoint        切点

* @param targetDataSource 动态数据源

*/

@After(“@annotation(targetDataSource)”)

public void doAfter(JoinPoint joinPoint, TargetDataSource targetDataSource) {


LOG.info(String.format(“当前数据源  %s  执行清理方法”, targetDataSource.dataSourceKey()));

DynamicDataSourceContextHolder.clear();

}

@Before(value = “pointCut()”)

public void doBeforeWithSlave(JoinPoint joinPoint) {


MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

//获取当前切点方法对象

Method method = methodSignature.getMethod();

if (method.getDeclaringClass().isInterface()) {//判断是否为借口方法

try {


//获取实际类型的方法对象

method = joinPoint.getTarget().getClass()

.getDeclaredMethod(joinPoint.getSignature().getName(), method.getParameterTypes());

} catch (NoSuchMethodException e) {


LOG.error(“方法不存在!”, e);

}

}

if (null == method.getAnnotation(TargetDataSource.class)) {


DynamicDataSourceContextHolder.setSlave();

}

}

}

7、在springboot程序运行入口中设置取消自动配置数据源

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

public class SpringBootDynamicDatasourceStartedApplication {

public static void main(String[] args) {


SpringApplication.run(SpringBootDynamicDatasourceStartedApplication.class, args);

}

}

8、使用,在你需要切换数据源的service方法上加上注解就OK,注意:如果你使用了接口对service层进行分离,那么注解需要添加到你的实现类的相关方法上。示例如下

@Service(“userInfoService”)

public class UserInfoServiceImpl implements UserInfoService {


private static final Logger LOG = Logger.getLogger(UserInfoServiceImpl.class);

@Resource

private UserInfoMapper userInfoMapper;

@TargetDataSource(dataSourceKey = DataSourceKey.DB_OTHER)

@Override

public List<UserInfo> listAll() {


return userInfoMapper.listAll();

}

//使用此注解来切换到想切换的数据源

@TargetDataSource(dataSourceKey = DataSourceKey.DB_OTHER)

@Override

public int insert(UserInfo userInfo) {


return userInfoMapper.insert(userInfo);

}

}

2.3 配置数据源(不用DruidDataSourceBuilder)

当然,上面也可以不用DruidDataSourceBuilder,想下面一样:

自定义 DataSource 类型的 @Bean 可以覆盖默认设置,

@Bean

@ConfigurationProperties(prefix=”app.datasource”)

public DataSource dataSource() {


return new FancyDataSource();

}

app.datasource.url=jdbc:h2:mem:mydb

app.datasource.username=sa

app.datasource.pool-size=30

Spring Boot也提供了一个工具类 DataSourceBuilder 用来创建标准的数据源。如果需要重用 DataSourceProperties 的配置,可以用它初始化一个 DataSourceBuilder :

@Bean

@ConfigurationProperties(“app.datasource”)

public DataSource dataSource() {


return DataSourceBuilder.create().build();

}

在此场景中,保留了通过Spring Boot暴露的标准属性,通过添加 @ConfigurationProperties ,可以暴露在相应的命命名空间暴露其他特定实现的配置,具体详情可以参考DataSourceAutoConfiguration

配置两个数据源

创建多个数据源和创建一个工作都是一样的,如果使用JDBC或JPA的默认自动配置,需要将其中一个设置为 @Primary (然后它就能被任何 @Autowired 注入获取)。

@Bean

@Primary

@ConfigurationProperties(“app.datasource.foo”)

public DataSourceProperties fooDataSourceProperties() {


return new DataSourceProperties();

}

@Bean

@Primary

@ConfigurationProperties(“app.datasource.foo”)

public DataSource fooDataSource() {


return fooDataSourceProperties().initializeDataSourceBuilder().build();

}

@Bean

@ConfigurationProperties(“app.datasource.bar”)

public DataSourceProperties barDataSourceProperties() {


return new DataSourceProperties();

}

@Bean

@ConfigurationProperties(“app.datasource.bar”)

public DataSource barDataSource() {


return barDataSourceProperties().initializeDataSourceBuilder().build();

}

app.datasource.foo.type=com.zaxxer.hikari.HikariDataSource

app.datasource.foo.maximum-pool-size=30

app.datasource.bar.url=jdbc:mysql://localhost/test

app.datasource.bar.username=dbuser

app.datasource.bar.password=dbpass

app.datasource.bar.max-total=30

3. 开启事务后数据源切换失败

问题:

进行数据源切换配置运行成功后,我们数据源切换的Service层加入事务控制,发现此时数据源切换失败,dao只会访问默认的数据源。出现这个问题的原因,我在网上找了一下:

1. AOP可以触发数据源字符串的切换,这个没问题

2. 数据源真正切换的关键是 AbstractRoutingDataSource 的 determineCurrentLookupKey() **被调用,此方法是在open connection**时触发

3. 事务是在connection层面管理的,启用事务后,一个事务内部的connection是复用的,所以就算AOP切了数据源字符串,但是数据源并不会被真正修改

也就是说将数据源切换和事务处理都放在Service层,则数据源切换时失效的。

3.1 方法:控制事务和切换顺序

https://www.jianshu.com/p/216e17c3a9ba

下面提供两个解决方案:

1,在dao实现事务控制:此种方式不太合理,在于再dao层加入事务控制,无法保证一个方法内的事务的一致性

2.将数据源切换和事务开启按顺序进行,先切换,再开启事务。如下所示:

@DataSource(“erp”)

List<ErpUserRole> findRelationList(String redisKey);

List<ErpUserRole> findRelationList1(String redisKey);

@Override

public List<ErpUserRole>  findRelationList(String redisKey){


return ((SyncErpDataService)AopContext.currentProxy()).findRelationList1(redisKey);

}

@Transactional(readOnly = true)

@Override

public List<ErpUserRole>  findRelationList1(String redisKey){


return erpUserRoleDao.findRelationList(redisKey);

}

先进行数据源切换,再通过代理的方式调用另一个方法,该方法上开启事务,访问dao层。  本人测试不通过代理的方式进行方法调用的话,仍然没法数据源切换成功!!!

切换数据源和开启事务分离:

控制器层->方法1(切换数据源,使用代理方式调用方法2)->方法2(开启事务,执行多个dao操作)

3.2 方法:重写MultiDataSourceTransaction事务

可以查看:

https://blog.csdn.net/gaoshili001/article/details/79378902

https://github.com/baomidou/dynamic-datasource-spring-boot-starter/issues/83

3.3 错误 jdbcUrl is required with driverClassName.

在Spring Boot 1.5.x升级到Spring Boot 2.0后,一些配置及用法有了变化,如果不小心就会碰到“jdbcUrl is required with driverClassName.”的错误,详细错误信息可以查看这里。

经过一番努力,查找资料,终于找到了解决办法,在这里分享出来,省得后来者再去查找资料。

第一种方法:

在配置文件中使用spring.datasource.jdbc-url,而不是通常使用的spring.datasource.url。

在这里也请大佬们帮忙解释下spring.datasource.jdbc-url和spring.datasource.url的区别。

第二种方法:

在数据源配置时使用DataSourceProperties方法。


最后,我的微信号是chinesedragon,二维码在最后,欢迎朋友们共同学习。

GITEE源码https://gitee.com/shupengluo/SpringBoot2.0-MultiDataSource

GITHUB源码https://github.com/luoshupeng/SpringBoot2.0-MultiDataSource

参考:https://my.oschina.net/chinesedragon/blog/1647846

3.4错误org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope

使用spring事务注解的时候遇到过这个问题吗?

下面我们来看两种写法,第一种

@Transactional

public UserEntity login1(UserEntity user) {


userDao.update(6);

if(userDao.update(6)){


TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

}

return user;

}

第二种,调用login()

public UserEntity login(UserEntity user) {


this.test();

return user;

}

@Transactional

public void test(){


userDao.update(6);

if(userDao.update(6)){


TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

}

}

第一种写法的时候,回滚是起作用的,而第二种写法的时候就会报错,错误就是一开始提到的。

为什么会这样呢?

spring里事务是用注解配置的,当一个方法没有接口,单单只是一个方法不是服务时,事务的注解是不起作用的,需要回滚时就会报错。

出现这个问题的根本原因在于AOP的实现原理。由于@Transactional 的实现原理是AOP,AOP的实现原理是动态代理,换句话说,自调用时不存在代理对象的调用,这时不会产生我们注解@Transactional 配置的参数,自然无效了。

虽然可以直接从容器中获取代理对象,但这样有侵入之嫌,不推荐。

sping的事务是通过注解配置上去的,而下面的那个方法并没有接口,在实现类里面只是一个简单的方法而已,对于事务的注解来说没有任何作用,所以在这个方法里面调用回滚的方法自然就报错了。

所以在以后的项目中如果你要使用事务,那么请记住,一个服务一个事务,一次请求一个事务,千万不要想着用调用方法,然后再一个方法上面加事务。你只能调用另外一个服务,在另外一个服务上面加事务。

也是在此记录一笔:

事务必须用在服务上,且一个服务一个事务,不得嵌套。

不能在Controller或server层同时开启事务和切换数据源,是无法在去切换数据源的

如果用切片: 切换数据源的order值要比事务切面的值小,这样优先级高!否则自动切换数据源将会失败!

目前我们create/update接口内部,调用多次数据库切换,所以不能开启事务

注意:(1)有@Transactional注解的方法,方法内部不可以做切换数据库操作

(2)在同一个service其他没有@Transactional注解的方法调用带@Transactional的方法,事务不起作用,

4.参考:

https://github.com/TavenYin/spring-dynamic-datasource

https://github.com/helloworlde/SpringBoot-DynamicDataSource

https://www.jianshu.com/p/0a485c965b8b

https://blog.csdn.net/twomr/article/details/79137056

https://github.com/baomidou/dynamic-datasource-spring-boot-starter/issues/83

https://blog.csdn.net/m0_37837382/article/details/81171393

https://www.cnblogs.com/jpfss/p/8295692.html

http://blog.zollty.com/b/archive/solution-of-spring-multiple-datasource.html

https://segmentfault.com/a/1190000015786019

https://juejin.im/post/5a927d23f265da4e7e10d740

1.基于Mybatis多SqlSession实例分开扫描各自Mapper

https://blog.csdn.net/isea533/article/details/46815385

http://www.cnblogs.com/ityouknow/p/6102399.html

https://blog.csdn.net/maoyeqiu/article/details/74011626

https://blog.csdn.net/neosmith/article/details/61202084

https://www.cnblogs.com/Alandre/p/6611813.html

2.动态AOP数据源

https://github.com/helloworlde/SpringBoot-DynamicDataSource/blob/roundrobin/src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicDataSourceContextHolder.java

https://github.com/baomidou/dynamic-datasource-spring-boot-starter/tree/master/src/main/java/com/baomidou/dynamic/datasource

https://www.hifreud.com/2017/07/06/spring-boot-18-data-access/

https://juejin.im/post/5c9f52de51882567c94e7184

3.另外一种思路:

https://github.com/hs-web/hsweb-framework/blob/master/hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/dynamic/DynamicSqlSessionFactory.java


————————————————

版权声明:本文为CSDN博主「zzhongcy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zzhongcy/article/details/103177280/