1. 概述
spring 的事务注解 @Transaction 相信很多人都用过, 而 @Transaction 默认配置适合 80% 的配置
本篇文章不是对 spring 注解事务做详细介绍, 而是解决一些实际场景下遇到的问题
2. 事务回滚
2.1 默认回滚策略
- @Transactional
- public void rollback() throws SQLException {
- // update db
- throw new SQLException("exception");
- }
上述代码事务会回滚吗? 不会的, 就算抛出 SQLException 了, 但是之前的数据库操作依然会提交, 原因就是 @Transactional 默认情况下只回滚 RuntimeException 和 Error
2.2 指定回滚异常
因此, 如果要指定哪些异常需要回滚, 则通过配置 @Transactional 中 rollbackFor, 例如
- @Transactional(rollbackFor = {SQLException.class})
- public void rollback() throws SQLException {
- // update db
- throw new SQLException("exception");
- }
按照上面例子, 那指定的 SQLException, 当抛出 RuntimeException 的时候, 还会回滚吗?
- @Transactional(rollbackFor = {SQLException.class})
- public void rollback() throws SQLException {
- // update db
- throw new Runtime("exception");
- }
还是会回滚的
2.3 事务嵌套的回滚
假设有下面的逻辑, 事务会回滚吗 (或者说 updateA,updateB,updateC) 哪些更新会提交
- @Transactional
- public void rollback() {
- // updateA
- try{
- innelTransaction()
- }catch(RuntimeException e){
- //do nothing
- }
- //updateC
- }
- @Transactional
- public void innelTransaction() throws SQLException {
- // updateB
- throw new RuntimeException("exception");
- }
答案是都会提交, 因为事务的回滚的匹配规则是嵌套最外层的为准 那如果换成下面的代码
- @Transactional
- public void rollback() throws SQLException{
- // updateA
- innelTransaction()
- //updateC
- }
- @Transactional(rollbackFor = SQLException.class)
- public void innelTransaction() throws SQLException {
- // updateB
- throw new SQLException("exception");
- }
updateA 和 updateB 依然会提交, updateC 不会提交(因为代码执行不到这里)
2.4 小结
所以, 在需要事务回滚的时候, 最好还是抛出 RuntimeException, 并且不要在代码中捕获此类异常
三事务传播性
@Transaction 中的 propagation 的可以配置事务的传播性, 网上介绍的很多, 就直接复制一段
PROPAGATION_REQUIRED-- 支持当前事务, 如果当前没有事务, 就新建一个事务这是最常见的选择 (也是默认策略)
PROPAGATION_SUPPORTS-- 支持当前事务, 如果当前没有事务, 就以非事务方式执行
PROPAGATION_MANDATORY-- 支持当前事务, 如果当前没有事务, 就抛出异常
PROPAGATION_REQUIRES_NEW-- 新建事务, 如果当前存在事务, 把当前事务挂起
PROPAGATION_NOT_SUPPORTED-- 以非事务方式执行操作, 如果当前存在事务, 就把当前事务挂起
PROPAGATION_NEVER-- 以非事务方式执行, 如果当前存在事务, 则抛出异常
3.1 如何在事务中读取最新配置
有时候需要在一个事务中, 读取最新数据 (默认是读取事务开始前的快照) 其实很简单, 只要使用上面 PROPAGATION_NOT_SUPPORTED 传播性就可以了
- @Transactional
- public void transaction() throws SQLException {
- // do something
- queryNewValue();
- }
- @Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void queryNewValue() throws SQLException {
- // 查询数据中的最新值
- }
你以为上面的能生效, 就错了, 在同一个类中调用事务方法, 不能直接简单调用
四内部调用事务方法
事务注解的实质就是在创建一个动态代理, 在调用事务方法前开启事务, 在事务方法结束以后决定是事务提交还是回滚
因此, 直接在类内部中调用事务方法, 是不会经过动态代理的
因此, 如果要使方法 B 点事务生效, 必须这样
4.1 解决办法
解决思路: 需要在内部调用方法 B 的时候, 找到当前类的代理类, 用代理类去调用方法 B
4.1.1 解决办法 1
- @Service
- public class MyService{
- @Transactional
- public void transaction(){
- // do something
- ((MyService) AopContext.currentProxy()).queryNewValue();
- }
- @Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void queryNewValue(){
- // 查询数据中的最新值
- }
- }
通过 AopContext.currentProxy()可以拿到当前类的代理类, 但是要使用这个时候, 必须在启动类上加上
@EnableAspectJAutoProxy(exposeProxy=true)
4.1.2 解决办法 2
既然是要拿到当前代理类, 那其实直接在 Spring 的容器里面去拿也可以啊在 spring 中拿 Bean 的方法大致有 2 种
通过注入
- @Service
- public class MyService{
- @Autowired
- private MyService self;
- @Transactional
- public void transaction() {
- // do something
- self.queryNewValue();
- }
- @Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void queryNewValue() {
- // 查询数据中的最新值
- }
- }
tips:spring 现在对一些循环依赖是提供支持的, 简单来说, 满足:
Bean 是单例
注入的方式不是构造函数注入
通过 BeanFactory
- @Service
- public class MyService implements BeanFactoryAware{
- private MyService self;
- @Transactional
- public void transaction(){
- // do something
- self.queryNewValue();
- }
- @Transactional(propagation = Propagation.NOT_SUPPORTED)
- public void queryNewValue() {
- // 查询数据中的最新值
- }
- @Override
- public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
- self = beanFactory.getBean(MyService.class);
- }
- }
虽然在前一种方法在大部分情况下是可行的, 但是有些情况下还是会报循环依赖的问题 (可以尝试在该类中即有 @Transaction, 又有 @Async) 而使用 BeanFactoryAware 来注入, 这样是在 Bean 初始完成后再 set 进来, 而不是依赖 Spring 来解决循环依赖
4.2 需要注意的地方
使用 @Transaction 注解的方法, 必须用 public 来修饰
其实不止是 @Transaction, 其他类似 @Cacheable,@Retryable 等依赖 spring proxy 也必须使用上述方式达到内部调用
来源: https://www.cnblogs.com/lizo/p/8534778.html