springboot dynamic多数据源demo以及常见切换、事务的问题
作者:一片星空~
一:引入依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.1</version> </dependency>
二:配置多数据源
yaml配置
通过yaml配置主数据源,这里就只配置了一个主数据源,后续通过代码来自由的切换数据源。
spring: datasource: dynamic: hikari: connection-timeout: 5000 idle-timeout: 30000 # 经过idle-timeout时间如果连接还处于空闲状态, 该连接会被回收 min-idle: 5 # 池中维护的最小空闲连接数, 默认为 10 个 max-pool-size: 16 # 池中最大连接数, 包括闲置和使用中的连接, 默认为 10 个 max-lifetime: 60000 # 如果一个连接超过了时长,且没有被使用, 连接会被回收 is-auto-commit: true primary: master #设置默认的数据源或者数据源组,默认值即为master strict: true #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源 datasource: master: # 数据源名称 url: username: password: driver-class-name: com.mysql.cj.jdbc.Driver # 如下,如果你是确定的几个数据源,可以直接都在yaml配置写死即可 # slave_1: # url: # username: # password: # driver-class-name: com.mysql.cj.jdbc.Driver 其中数据库连接池,所有的数据库统一配置,也可以单独配置,例如: datasource: master: # 数据源名称 url: username: password: driver-class-name: com.mysql.cj.jdbc.Driver hikari: connection-timeout: 5000 idle-timeout: 30000 # 经过idle-timeout时间如果连接还处于空闲状态, 该连接会被回收 min-idle: 5 # 池中维护的最小空闲连接数, 默认为 10 个 max-pool-size: 16 # 池中最大连接数, 包括闲置和使用中的连接, 默认为 10 个 max-lifetime: 60000 # 如果一个连接超过了时长,且没有被使用, 连接会被回收 is-auto-commit: true
三:切换数据源DS注解
DS放在哪里合适?
首先开发者要了解的基础知识是,DS注解是基于AOP的原理实现的,aop的常见失效场景应清楚。
比如内部调用失效,shiro代理失效。
具体见切换数据源:
- 1.通常建议DS放在serviceImpl的方法上,如事务注解一样。(常用方式二:访问第三方库api,单独抽取接口,并作数据处理)
- 2.注解在Controller的方法上或类上:并不是不可以,并不建议的原因主要是controller主要作用是参数的检验等一些基础逻辑的处理,这部分操作常常并不涉及数据库
- 3.注解在service的实现类的方法或类上:这是建议的方式,service主要是对业务的处理, 在复杂的场景涉及连续切换不同的数据库。 如果你的方法有通用性,其他service也会调用你的方法。 这样别人就不用重复处理切换数据源
- 4.注解在mapper上。(常用方式一):通常如果你某个Mapper对应的表只在确定的一个库,也是可以的。 但是建议只注解在Mapper的类上。
- 5.其他使用方式:继承抽象类上的DS
- 6.继承接口上的DS
示例:
@Service @DS("common") public class BookService extends ServiceImpl<BookMapper, Book> { @Resource private BookMapper bookMapper; @Transactional(propagation = Propagation.REQUIRES_NEW) public void save(ReqDto reqDto) { bookMapper.save(reqDto); } }
等。
四:切换数据源以及事务相关问题
1.使用动态数据源(@DS)时
@Transactional使用不当会照成@DS失效。
- 1.实现层上面加@Transactional,数据源没有切换
- 2.开启事务的同时,会从数据库连接池获取数据库连接;
- 3.如果内层的service使用@DS切换数据源,只是又做了一层拦截,但是并没有改变整个事务的连接;
- 4.在这个事务内的所有数据库操作,都是在事务连接建立之后,所以会产生数据源没有切换的问题;
- 5.为了使@DS起作用,必须替换数据库连接,也就是改变事务的传播机智,产生新的事务,获取新的数据库连接
解决方法:
- 去除MasterService.upload上面的@Transactional,数据源切换正常,虽然可以解决,但是事务无效。
- BookService的save上面加@Transactional(propagation =Propagation.REQUIRES_NEW),数据源切换,且事务有效。
- 完美解决。它会重新创建新事务,获取新的数据库连接,从而得到@DS的数据源
2.@Transaction开启了事务
为什么多数据源事务不生效?
@Transaction开启了事务,为什么多数据源事务不生效? 简单来说:嵌套数据源的service中,如果操作了多个数据源,不能在最外层加上@Transaction开启事务,否则切换数据源不生效,因为这属于分布式事务了,需要用seata方案解决,如果是单个数据源(不需要切换数据源)可以用@Transaction开启事务,保证每个数据源自己的完整性
加事务不生效的原因:dynamic-datasource切换数据源的原理就是实现了DataSource接口,实现了getConnection方法,只要在service中开启事务,service中对其他数据源操作只会使用开启事务的数据源,因为开启事务数据源会被缓存下来,可以在DataSourceTransactionManager的doBegin方法中看见那个txObject,如果在一个事务内,就会复用Connection,所以切换不了数据源
解决方法:本地事务
通过本地事务实现很简单,就是循环提交,发生错误,循环回滚。 我们默认的前提是数据库本身不会异常,比如宕机。如数据在回滚的过程突然宕机,本地事务就会有问题。如果你需要完整分布式方案请使用seata方案。
使用方法
在最外层的方法添加 @DSTransactional,底下调用的各个类就正常切换数据源即可。
简单举例如下:
@DeleteMapping //只要@DSTransactional注解下任一环节发生异常,则全局多数据源事务回滚。 @DSTransactional() @ApiOperation("删除数据源") public String remove(String poolName) { DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource; ds.removeDataSource(poolName); return "删除成功"; } 但一定要注意Spring事务@Transational和本地事务@DSTransactional,不能混用
3.其余问题了解
一:涉及需要切换数据源时
1.不能使用事务,否则数据源不会切换,使用的还是是第一次加载的数据源 。
删除 操作多数据源的方法或者类、接口 上的 注解 @Transactional() 即可。
2.第一次加载的数据源之后,第二次(第三次...)操作其它数据源,如果数据源不存在,使用的还是第一次加载的数据源
3.数据源名称最好不要包含下滑线,下滑线的数据源切换不了
二:其他
1.接口中A、B两个方法,A无@Transactional标签,B有,上层通过A间接调用B,此时事务不生效。
2.接口中异常(运行时异常)被捕获而没有被抛出。默认配置下,spring 只有在抛出的异常为运行时 unchecked 异常时才回滚该事务,也就是抛出的异常为RuntimeException 的子类(Errors也会导致事务回滚),而抛出 checked 异常则不会导致事务回滚 。可通过 @Transactional rollbackFor进行配置。
3.多线程下事务管理因为线程不属于 spring 托管,故线程不能够默认使用 spring 的事务,也不能获取spring 注入的 bean 。在被 spring 声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制。一个使用了@Transactional 的方法,如果方法内包含多线程的使用,方法内部出现异常,不会回滚线程中调用方法的事务。
引用:
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。