1 . 项目目标

  1. 实现 不同数据源的切换 (使用AbstractRoutingDataSource)
  2. 不同数据源之间,事物管理,多个数据源之间同时commit或者同时rollback
  3. 兼容不同的连接池(dbpc,druid)
  4. 兼容mybatis,JPA 等不同的方式 (spring- boot -starter)

以上就是该项目实现的所有功能,因代码量和篇幅的问题,本文只写大概的实现思路,中间遇到的坑,以及我封装的 spring-starter 的用法

如果有其他实现上的问题可以加QQ随意聊.

话不多数先贴github

github

gitee

2. 项目简介

本框架用 spring-starter的模式封装, 可以直接使用以下maven坐标快速使用

<dependency><groupId>io.github.cao2068959</groupId><artifactId>multidatasource-spring-starter</artifactId><version>1.1.0</version>
</dependency>

其他 mysql驱动这个不用我多说了吧.

然后会根据是否引入了 mybatis-starter和JPA-starter 来自动创建数据源,自动配置类如下,同时也是项目的入口

在 application.yaml 中配置 对应的数据源

这配置了2个数据源 .第一个 使用了 druid连接池 , 第二个使用了 spring data 默认的形式

然后在项目中就可以直接使用 下图为 mybatis的方式

用 @DataSource 来指定使用什么连接池.

如果是 JPA的形式.则

@Repository
@DataSource("userdb")
public interface UserDao extends JpaRepository<User,Integer>, JpaMulti {}

注意这里 需要继承 接口 JpaMulti 不然aop不能正确扫描到

然后是事物,我这里多个数据源,我想让他同时提交,获取异常后同时回滚,这里使用了一个注解

@TransactionMulti 来指定要事物管理的数据源
    @TransactionMultipublic void setUser(){Random random = new Random();//使用了storedb数据源的写操作userMapper.setEmployee(random.nextInt(10000),"小绿","12345",11,new Date(),new Date());//使用了userdb数据源的读操作userMapper.setUser(random.nextInt(10000),"小红","12345",14,1,new Date(),new Date());int i = 2 /0;}

这里的 setEmployee 和 setUser 分别连接的是不同的数据源. 抛出异常后,2个数据都没有插入数据库

以上就是本项目的基本功能,以下是基本实现思路

3 . 自动数据源切换

这里 使用了 类  AbstractRoutingDataSource 这是一个模版类,主要实现方法  determineCurrentLookupKey()

这个数据源里有个 map 存入了自定义的多个数据源,在调用 getConnect 方法的时候,会去根据 自定义 determineCurrentLookupKey() 方法去获取对应的key,然后拿到正真的数据源.


public class DataSourceRouting extends AbstractRoutingDataSource {ThreadLocal<String> threadLocal = new ThreadLocal<>();//把当前事物下的连接塞入,用于事物处理ThreadLocal<Map<String,ConnectWarp>> connectionThreadLocal = new ThreadLocal<>();//这里只是留一个备份,切换数据源的时候,如果没有对应ke就直接异常,真正调用会传给AbstractRoutingDataSource处理//这里只读,没有线程安全问题Map<String, DataSource> dataSourceMap = new HashMap<>();@Overrideprotected Object determineCurrentLookupKey() {String currentName = threadLocal.get();//没有时,拿第一个if(currentName == null){currentName = dataSourceMap.keySet().iterator().next();}return currentName;}
}

然后是用aop来扫描 @DataSourse 注解,从而动态切换数据源

@Aspect
public class DataSourceChangeAop {@Autowiredprivate DataSourceRouting dataSourceRouting;@Pointcut("@annotation(chy.frame.multidatasourcespringstarter.annotation.DataSource)")public void annotationPointcut(){}@Pointcut("this(chy.frame.multidatasourcespringstarter.annotation.JpaMulti)")public void interfacePoint(){}@Before("annotationPointcut()")public void beforMethod(JoinPoint point){MethodSignature methodSignature = (MethodSignature) point.getSignature();Method method = methodSignature.getMethod();DataSource annotation = method.getAnnotation(DataSource.class);String value = annotation.value();//切换了数据源dataSourceRouting.changeDataSource(value);}@Before("interfacePoint()")public void interfacePointBefore(JoinPoint point) throws Exception {//获取代理对象上所有的接口Class<?>[] interfaces = point.getTarget().getClass().getInterfaces();//扫描上面的DataSource 注解for (Class<?> anInterface : interfaces) {DataSource annotation = anInterface.getAnnotation(DataSource.class);if(annotation == null){continue;}String value = annotation.value();//切换了数据源dataSourceRouting.changeDataSource(value);return;}//dataSourceRouting.changeDataSource(value);}@After("annotationPointcut() ||interfacePoint() ")public void After(JoinPoint point) throws Exception {dataSourceRouting.clearThreadLocal();}}

4 . 事物管理

spring 的事物管理器  DataSourceTransactionManager

但是绑定到当前线程中后,每次拿connect就 不会调用 determineCurrentLookupKey() 方法去获取 不同的数据源从而拿到不同的connect,而是直接去 拿这里绑定的 connect, 所以使用 原生的事物管理器,并不能完成我们需要的功能.

所以我决定直接在aop中拿 connect来开启事物

以下是 事物管理的 aop

@Aspect
public class MultiTransactionManagerAop {@AutowiredDataSourceRouting dataSourceRouting;@Pointcut("@annotation(chy.frame.multidatasourcespringstarter.annotation.TransactionMulti)")public void annotationPointcut() {}@Around("annotationPointcut()")public void roundExecute(ProceedingJoinPoint joinpoint) throws Throwable {MethodSignature methodSignature = (MethodSignature) joinpoint.getSignature();Method method = methodSignature.getMethod();TransactionMulti annotation = method.getAnnotation(TransactionMulti.class);int transactionType = annotation.transactionType();//开启事务dataSourceRouting.beginTransaction(transactionType);//正真执行了 方法joinpoint.proceed();//提交事务dataSourceRouting.commitTransaction();}@AfterThrowing(pointcut = "annotationPointcut()", throwing = "e")public void handleThrowing(JoinPoint joinPoint, Exception e) {//controller类抛出的异常在这边捕获try {//回滚事物dataSourceRouting.rollbackTransaction();} catch (SQLException e1) {e1.printStackTrace();}}}

同时这里有 1个问题要解决

1 . 因为现在事物是我自己管理,但是mybatis 每次拿完 connect就会自动 调用close 和 commit方法.这样导致我自己操作的事物失效.所以我要先让mybatis不能自作主张帮我关闭connect

解决方法: spring data获取connect的时候,给他包装类(或者代理类),覆盖了原来的close和commit方法

类   class DataSourceRouting extends AbstractRoutingDataSource 中覆盖

/*** 如果 在connectionThreadLocal 中有 说明开启了事物,就从这里面拿** @return* @throws SQLException*/@Overridepublic Connection getConnection() throws SQLException {Optional<TransactionCarrier> currentTransactionCarrier = getCurrentTransactionCarrier();if (currentTransactionCarrier.isPresent()) {TransactionCarrier transactionCarrier = currentTransactionCarrier.get();//开了事物 那么从 currentTransactionCarrier中去获取对应的 connect;String currentName = (String) determineCurrentLookupKey();Optional<Connection> transactionConnect = transactionCarrier.getConnect(currentName);//使用了已经开启了事务的connect;if (transactionConnect.isPresent()) {return transactionConnect.get();}//开启事物后第一次获取connect, 那么先获取一个新的 connectConnection connection = new ConnectWarp(determineTargetDataSource().getConnection());//把新获取到的 connection 放入 transactionCarrier中,后续再次获取就能直接拿到transactionCarrier.addTransactionConnect(currentName, connection);return connection;} else {//没开事物 直接走return determineTargetDataSource().getConnection();}}

而这个包装里中

这样只有我手动 调用 commit(true) 和 close(true) 才会正真提交和关闭连接

提交事务和回滚事务的主逻辑如下:

 /*** 提交事物** @throws SQLException*/public void commitTransaction() throws SQLException {getCurrentTransactionCarrier().orElseThrow(() -> new SqlTransactionException("当前线程中事物没有开启")).commitTransaction();//提交事物后清理释放资源clearTransaction();}public void commitTransaction() throws SQLException {for (Map.Entry<String, Connection> connectionEntry : transactionConnects.entrySet()) {Connection connection = connectionEntry.getValue();if (!(connection instanceof ConnectWarp)){continue;}ConnectWarp connectWarp = (ConnectWarp) connection;connectWarp.commit(true);connectWarp.close(true);}}/*** 撤销事物** @throws SQLException*/public void rollbackTransaction() throws SQLException {getCurrentTransactionCarrier().orElseThrow(() -> new SqlTransactionException("当前线程中事物没有开启")).rollbackTransaction();//提交事物后清理释放资源clearTransaction();}public void rollbackTransaction() throws SQLException {for (Map.Entry<String, Connection> connectionEntry : transactionConnects.entrySet()) {Connection connection = connectionEntry.getValue();if (!(connection instanceof ConnectWarp)){continue;}ConnectWarp connectWarp = (ConnectWarp) connection;connectWarp.rollback();connectWarp.close(true);}}

spring boot多数据源动态切换, 多数据源事务管理相关推荐

  1. 【仿牛客网笔记】 Spring Boot进阶,开发社区核心功能-事务管理

    添加评论中会用到事务管理. 解决的程度不同,层级不同.我们一般选择中间的级别. 选择时既能满足业务的需要,又能保证业务的安全性,在这样的前提下我们追求一个更高的性能. 第一类丢失更新 图中是没有事务隔 ...

  2. Spring+Mybatis多数据源配置(四)——AbstractRoutingDataSource实现数据源动态切换

    欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...

  3. springboot使用mybatis多数据源动态切换的实现

    需求:项目使用了读写分离,或者数据进行了分库处理,我们希望在操作不同的数据库的时候,我们的程序能够动态的切换到相应的数据库,执行相关的操作. 首先,你需要一个能够正常运行的springboot项目,配 ...

  4. Proxool配置多数据源动态切换

    2019独角兽企业重金招聘Python工程师标准>>> 前段时间遇到多数据源动态切换问题,总结一下,做个记录,以备后续之需! 首先附上proxool连接池的配置方法:http://3 ...

  5. springboot多数据源动态切换和自定义mybatis分页插件

    1.配置多数据源 增加druid依赖 完整pom文件 数据源配置文件 route.datasource.driver-class-name= com.mysql.jdbc.Driver route.d ...

  6. SpringBoot+AOP实现多数据源动态切换

    SpringBoot+AOP实现多数据源动态切换 背景 设计总体思路 步骤 背景 系统后端需要访问多个数据库,现有的数据库连接配置写入配置文件中.后端需要从一个数据库的配置表里动态的读取其它mysql ...

  7. Spring-Boot + AOP实现多数据源动态切换

    2019独角兽企业重金招聘Python工程师标准>>> 最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据 ...

  8. Spring Boot 中使用 Hikari连接各类数据源

    Spring Boot 中使用 Hikari连接各类数据源 1,连接hive集群 HikariConfig中各属性设置 {"allowPoolSuspension": false, ...

  9. Spring Boot使用spring-data-jpa配置Mysql多数据源

    转载请注明出处 :Spring Boot使用spring-data-jpa配置Mysql多数据源 我们在之前的文章中已经学习了Spring Boot中使用mysql数据库 在单数据源的情况下,Spri ...

最新文章

  1. 鸟哥的linux 实训教程,鸟哥的Linux基础学习实训教程
  2. docker镜像创建redis5.0.3容器集群
  3. 对网页是否为当前展示标签页、是否最小化、以及是否后台运行进行监听
  4. 个人博客作业week7
  5. .NET 2.0 CER学习笔记
  6. 创建springmvc配置
  7. 【转】LAMP网站架构方案分析【精辟】
  8. jeecg自定义结果集t:dictSelect
  9. [Lucene.Net] 基本用法
  10. 微服务SpringCloud中的负载均衡,你都会么?
  11. 2022年引领全球增长的60大技术:机会、增长、投资、洞察
  12. java实现webservice
  13. 【整理】Laravel中Eloquent ORM 关联关系的操作
  14. linux 下svn安装
  15. Linux基础之tr与重定向管道
  16. Nmap小技巧——探测大网络空间、局域网中的存活主机
  17. 《Option Volatility Pricing》阅读笔记之 Option Terminology (期权术语)
  18. C. K-th Not Divisible by n
  19. BDLS协议重磅发布 — Sperax启动Bug Bounty计划
  20. mql4 c语言,MQL4基础:运算符和表达式 -

热门文章

  1. 中国计算机用户协会秘书长,图文:中国计算机用户协会秘书长唐群演讲
  2. React-Hooks之useImperativeHandle使用,让父组件操纵子组件的状态和方法
  3. 大二上学期(回顾与展望)
  4. 不同朝向的房间,怎么选择舒适的墙布颜色?-江南爱窗帘十大品牌
  5. [转]2009年最佳80后科技创业者
  6. 软件设计:一个简单的装修工程管理系统。
  7. 清华大学五道口金融学院考博难度解析及经验分享
  8. windows如何利用计划任务自动关机?
  9. 2021年全国计算机等级考试时间 什么时候考试
  10. 计算机专业html5的毕业论文范文,计算机专业毕业论文格式范文参考