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/