目录
一:引入依赖
二:配置多数据源
三:切换数据源DS注解
四:切换数据源以及事务相关问题:
1.使用动态数据源(@DS)时,@Transactional使用不当会照成@DS失效。
2.@Transaction开启了事务,为什么多数据源事务不生效?
3.其余问题了解
com.baomidou
dynamic-datasource-spring-boot-starter
3.5.1
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注解是基于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 {
@Resource
private BookMapper bookMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(ReqDto reqDto) {
bookMapper.save(reqDto);
}
}
等。
1.实现层上面加@Transactional,数据源没有切换
2.开启事务的同时,会从数据库连接池获取数据库连接;
3.如果内层的service使用@DS切换数据源,只是又做了一层拦截,但是并没有改变整个事务的连接;
4.在这个事务内的所有数据库操作,都是在事务连接建立之后,所以会产生数据源没有切换的问题;
5.为了使@DS起作用,必须替换数据库连接,也就是改变事务的传播机智,产生新的事务,获取新的数据库连接
解决方法:
去除MasterService.upload上面的@Transactional,数据源切换正常,虽然可以解决,但是事务无效。
BookService的save上面加@Transactional(propagation =Propagation.REQUIRES_NEW),数据源切换,且事务有效。完美解决。它会重新创建新事务,获取新的数据库连接,从而得到@DS的数据源
@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,不能混用
一:涉及需要切换数据源时
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 的方法,如果方法内包含多线程的使用,方法内部出现异常,
不会回滚线程中调用方法的事务。
引用:
参考博客:实现多租户动态切换数据源