数据源切换
创始人
2025-05-31 00:41:54
0

目录

应用场景

疑问

将就吧

数据源管理方案

AbstractRoutingDataSource

使用MyBatis注册多个SqlSessionFactory

使用dynamic-datasource框架


应用场景

疑问

我感觉这个好像没啥应用场景啊,一个服务对应一个数据库不是挺好的嘛,你这个服务还可以访问其他的数据库资源,那不是乱套了嘛

将就吧

就这么用呗,借鉴一下这个思想

数据源管理方案

AbstractRoutingDataSource

使用Spring提供的AbstractRoutingDataSource,这种方式的核心是使用Spring提供的AbstractRoutingDataSource抽象类,注入多个数据源

@Component
@Primary   // 将该Bean设置为主要注入Bean
public class DynamicDataSource extends AbstractRoutingDataSource {// 当前使用的数据源标识public static ThreadLocal name=new ThreadLocal<>();// 写@AutowiredDataSource dataSource1;// 读@AutowiredDataSource dataSource2;// 返回当前数据源标识@Overrideprotected Object determineCurrentLookupKey() {return name.get();}@Overridepublic void afterPropertiesSet() {// 为targetDataSources初始化所有数据源Map targetDataSources=new HashMap<>();targetDataSources.put("W",dataSource1);targetDataSources.put("R",dataSource2);super.setTargetDataSources(targetDataSources);// 为defaultTargetDataSource 设置默认的数据源super.setDefaultTargetDataSource(dataSource1);super.afterPropertiesSet();}
}

将自己实现的DynamicDataSource注册成为默认的DataSource实例后,只需要在每次使用 DataSource时,提前改变一下其中的name标识,就可以快速切换数据源。

@Component
@Aspect
public class DynamicDataSourceAspect implements Ordered {// 前置// 在每个访问数据库的方法执行前执行。@Before("within(com.tuling.dynamic.datasource.service.impl.*) && @annotation(wr)")public void before(JoinPoint point, WR wr){String name = wr.value();DynamicDataSource.name.set(name);System.out.println(name);}@Overridepublic int getOrder() {return 0;}// 环绕通知
}

处理流程

1. 首先是通过接口进行访问,准备读数据库

2. 然后到了前置aop进行功能增强

这里就顺便看一下入参都是啥东西,point我感觉就是代理的方法,这边这代理是cglib代理,而下面那个注解用的代理则是动态代理

这里面就是通过你注解里面设的值放到DynamicDataSource.name里面去

然后幸亏之前看了点mybatis源码,datasource设置的清清楚楚

两个数据源,每个数据源里面已经定义好了连接地址等配置信息,还有我们之前配置的targetDatasource和defaultTargetDataSource 

我们顺便再来看一下,mybatis底层这个数据源是啥选择的,是吧都已经看到这里了,就再往下看看呗

public class DynamicDataSource extends AbstractRoutingDataSource

还是得从这里看起,因为我们选择的是路由数据源的方式,因为我们继承了abstractRoutingDataSource,我们选择重写这两个方法,一个是选择路由到哪个数据源,一个是看名字我猜是,在所有配置信息完成后的操作,我们这里面配置了目标数据源map的定义,然后是默认数据源,以及再走父类的afterProperties方法

// 返回当前数据源标识@Overrideprotected Object determineCurrentLookupKey() {return name.get();}@Overridepublic void afterPropertiesSet() {// 为targetDataSources初始化所有数据源Map targetDataSources=new HashMap<>();targetDataSources.put("W",dataSource1);targetDataSources.put("R",dataSource2);super.setTargetDataSources(targetDataSources);// 为defaultTargetDataSource 设置默认的数据源super.setDefaultTargetDataSource(dataSource1);super.afterPropertiesSet();}

这里还看不出来那个lookupkey是干嘛用的,继续往下看,走到父类的方法中

public void afterPropertiesSet() {if (this.targetDataSources == null) {throw new IllegalArgumentException("Property 'targetDataSources' is required");} else {this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());this.targetDataSources.forEach((key, value) -> {Object lookupKey = this.resolveSpecifiedLookupKey(key);DataSource dataSource = this.resolveSpecifiedDataSource(value);this.resolvedDataSources.put(lookupKey, dataSource);});if (this.defaultTargetDataSource != null) {this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);}}}

这里面代码也很好懂,定义最后要用的数据源的map和默认最后用哪个数据源的map,我自己理解的

看到这里我就去找怎么确认最后使用哪个数据源,发现有这么个方法,我猜这个是决定使用哪个数据源,看看下面就是通过这个lookupkey找数据源

protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");Object lookupKey = this.determineCurrentLookupKey();DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);if (dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.resolvedDefaultDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");} else {return dataSource;}}
Object lookupKey = this.determineCurrentLookupKey();// 我们看到有这么一句话

我们来看看这个设置的是个啥,接着找实现类

@Nullableprotected abstract Object determineCurrentLookupKey();

哦豁,正好是我们重写的方法,那就直接进去呗,就是我们之前定义的w,r

@Overrideprotected Object determineCurrentLookupKey() {return name.get();}

我们这里面还是这么定义的,通过threadLocal,每个线程有这么一份变量

public static ThreadLocal name=new ThreadLocal<>();

我们通过这么一设置,到时候通过以上的步骤,等到使用mybatis操作数据库的时候,就知道最终使用的是哪个数据源了

使用MyBatis注册多个SqlSessionFactory

看一下配置类定义的

@Configuration
// 继承mybatis:
// 1. 指定扫描的mapper接口包(主库)
// 2. 指定使用sqlSessionFactory是哪个(主库)
@MapperScan(basePackages = "com.tuling.datasource.dynamic.mybatis.mapper.r",sqlSessionFactoryRef="rSqlSessionFactory")
public class RMyBatisConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource.datasource2")public DataSource dataSource2() {// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSourcereturn DruidDataSourceBuilder.create().build();}@Bean@Primarypublic SqlSessionFactory rSqlSessionFactory()throws Exception {final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();// 指定主库sessionFactory.setDataSource(dataSource2());// 指定主库对应的mapper.xml文件/*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/r/*.xml"));*/return sessionFactory.getObject();}@Beanpublic DataSourceTransactionManager rTransactionManager(){DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource2());return dataSourceTransactionManager;}@Beanpublic TransactionTemplate rTransactionTemplate(){return new TransactionTemplate(rTransactionManager());}
}

没搞明白他这个事务管理器和模板干啥用的

配置完事好像直接就能使用了

使用dynamic-datasource框架

dynamic-datasource是MyBaits-plus作者设计的一个多数据源开源方案。使用这个框架需要引入对应的pom依赖

com.baomidoudynamic-datasource-spring-boot-starter3.5.0

这样就可以在SpringBoot的配置文件中直接配置多个数据源。

spring:datasource:dynamic:#设置默认的数据源或者数据源组,默认值即为masterprimary: master#严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源strict: falsedatasource:master:url: jdbc:mysql://127.0.0.1:3306/datasource1?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=falseusername: rootpassword: rootinitial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driverslave_1:url: jdbc:mysql://127.0.0.1:3306/datasource2?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=falseusername: rootpassword: rootinitial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driver

这样就配置完成了master和slave_1两个数据库。

接下来在使用时,只要在对应的方法或者类上添加@DS注解即可。例如

@Service
public class FriendImplService implements FriendService {@AutowiredFriendMapper friendMapper;@Override@DS("slave_1")  // 从库, 如果按照下划线命名方式配置多个  , 可以指定前缀即可(组名)public List list() {return friendMapper.list();}@Override@DS("master")public void save(Friend friend) {friendMapper.save(friend);}//    @DS("master")
//    @DSTransactional
//    public void saveAll(){
//        // 执行多数据源的操作
//    }}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...