@Transactional与@DS冲突原因及解决方法
例子@Service@DB("Master")public class MasterService {@AutowiredUserService userService;@AutowiredBookService bookService;/**必须master库方法先执行,才能回滚,达到事务效果*/@Transactional(rollbackFor = Exception.class)publi
例子(看完请看下方的补充说明)
@Service
@DB("Master")
public class MasterService {
@Autowired
UserService userService;
@Autowired
BookService bookService;
/**必须master库方法先执行,才能回滚,达到事务效果*/
@Transactional(rollbackFor = Exception.class)
public void upload(ReqDto reqDto){
userService.save(reqDto); //@DB("Master")
bookService.save(reqDto); //@DB("common")
}
}
@Service
@DS("common")
public class BookService extends ServiceImpl<BookMapper, Book> {
@Resource
private BookMapper bookMapper;
public void save(ReqDto reqDto) {
bookMapper.save(reqDto);
}
}
结果调用报错。
冲突原因
使用动态数据源(@DS)时,@Transactional使用不当会照成@DS失效。
- BookService的save上面加@Transactional,数据源没有切换
- 开启事务的同时,会从数据库连接池获取数据库连接;
- 如果内层的service使用@DS切换数据源,只是又做了一层拦截,但是并没有改变整个事务的连接;
- 在这个事务内的所有数据库操作,都是在事务连接建立之后,所以会产生数据源没有切换的问题;
- 为了使@DS起作用,必须替换数据库连接,也就是改变事务的传播机智,产生新的事务,获取新的数据库连接;
解决方法
- 去除MasterService.upload上面的@Transactional,数据源切换正常,虽然可以解决,但是事务无效。
- BookService的save上面加@Transactional(propagation =Propagation.REQUIRES_NEW),数据源切换,且事务有效。完美解决。它会重新创建新事务,获取新的数据库连接,从而得到@DS的数据源
最终代码如下,只需要修改的是bookService,其他不变
@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);
}
}
注意
master:userService
common:bookService
- common数据库的操作,需要在master之后,这样当bookService.save失败,会使得userService回滚;
- 如果common的操作先,那当userService失败,无法使bookService回滚,这是因为Propagation.REQUIRES_NEW原因,具体可以百度Propagation.REQUIRES_NEW的作用。
会回滚
@Transactional(rollbackFor = Exception.class)
public void upload(ReqDto respDto){
userService.save(respDto);
bookService.save(respDto);
}
不会回滚
@Transactional(rollbackFor = Exception.class)
public void upload(ReqDto respDto){
bookService.save(respDto);
userService.save(respDto);
}
补充说明
这里要先感谢一下 @huihui-Arenas 的评论。
上方的方式虽然能解决问题,但是也有不好的地方:
- 调用顺序有限制
- 前面例子只有2个数据源,如果是10个,前5个成功,第6个失败的时候,前五个回滚困难
- 每个都要加
@Transactional
而且还可能需要Propagation.REQUIRES_NEW
启动新事务 - …
能不能使用一个注解就搞定多数据源的那种呢?答案是有的
没错。就是评论所说的,使用@DSTransactional
注解,就可以完美解决上面的问题。
需要导入包(版本太低的话可能没有@DSTransactional
注解,建议提高版本)
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
使用方式如下:
/**
* 在这里加了@DSTransactional注解,db1,db2,db3基本都不需要加事务注解了
*/
@DSTransactional
public void db() throws Exception {
inqOrderService.db1(); //数据源1
wxUserService.db2(); //数据源2
scanServiceImpl.db3(); //数据源3(这里抛出异常)
}
如上代码所示,当db3抛出异常时,db1,db2会跟着回滚
总结:
词穷,就一句话:只有一个服务,且用到多个数据源时,用@DSTransactional
注解比较方便,可以控制多数据源进行回滚。而不使用前面的例子的方式。
另外为什么说只有一个服务才用@DSTransactional
注解,多服务不行吗?没错,就是不行,如果系统是微服务架构,db1,db2,db3都来源于不同的服务,那么db3报错时,前面两个并不会回滚,因为他们不在一个服务内,@DSTransactional
注解排不上用场。此时只能使用另外一种方式了。网上有人说用seata
框架,也有人说用队列,在下有空研究研究,先这样,后会有其。
参考链接:https://blog.csdn.net/weixin_43846916/article/details/110824726
更多推荐
所有评论(0)