Spring 事务失效场景

1. 方法访问权限问题

java 访问权限有四种:private、default、protected、public, 它们的权限从左到右,依次变大。
在这里插入图片描述例如:我们把方法的访问权限定义成非public,这样会导致事务失效

@Transactional
protected void update(WxGzhBean wxGzhBean) {
    wxGzhMapper.update(wxGzhBean);
    throw new RuntimeException("异常");
}

因为:在AbstractFallbackTransactionAttributeSource 类的 computeTransactionAttribute 方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务,所以想用事务就必须把它定义成public。
在这里插入图片描述

2. 方法用final、static修饰

有时候,某个方法不想被子类重写,这时可以将方法定义成final,普通方法这样定义是没有问题的,但如果是将事务方法定义成final,那么会导致事务失效,例如

@Transactional
public final void update(WxGzhBean wxGzhBean) {
    wxGzhMapper.update(wxGzhBean);
    throw new RuntimeException("异常");
}
// idea 也会提示你:@Transactional方法必须要可以被重写

因为:spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现事务功能。如果某个方法用final修饰了,那么在它的代理类中就无法重写该方法实现事务功能。

同样,如果用static修饰,也是不能被动态代理的,static是随着类的加载而加载,是属于类, 所以静态方法是不能被重写的

3. 自调失效问题

在同一个类中,没有事务的A 去调用有事务的B,当B出现异常时事务不会回滚,出现自调失效问题

@Service
public class GzhService {   
    public void update(WxGzhBean wxGzhBean) { 
        updateState(wxGzhBean);
    }
    
    @Transactional
    public void updateState(WxGzhBean wxGzhBean) {
        wxGzhMapper.update(wxGzhBean);
        throw new RuntimeException("异常");
    }
}

原因:因为这种方式是直接调用了this对象的方法,并没有走代理对象,所以事务失效了。
解决:把自己注入进来,通过代理对象去调用

@Service
public class GzhService {    
    @Autowired
    private GzhService gzhService;
 
    public void update(WxGzhBean wxGzhBean) {
        gzhService.updateState(wxGzhBean);
    }

    @Transactional
    public void updateState(WxGzhBean wxGzhBean) {
        wxGzhMapper.update(wxGzhBean);
        throw new RuntimeException("异常");
    }
}

4. 未被spring管理

使用spring事务的前提是:对象要被spring管理,需要创建bean实例。
通常情况下,我们通过@Controller、@Service、@Component等注解,可以自动实现bean实例化和依赖注入的功能。
例如,忘记给Service类加@Service注解:

// @Service
public class GzhService {        
    @Transactional
    public void updateState(WxGzhBean wxGzhBean) {
        wxGzhMapper.update(wxGzhBean);
        throw new RuntimeException("异常");
    }
}

5. 多线程调用

事务方法A中调用了事务方法B,但是事务方法B是在另外一个线程中调用的。这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果B方法中抛出了异常,A方法也跟着回滚是不可能的。例如:

@Service
public class GzhService {   
    @Transactional
    public void updateState(WxGzhBean wxGzhBean) { 
        Thread t = new Thread(() -> {
            subService.updateState(wxGzhBean);
            throw new NullPointerException("异常");
        });
        t.start(); 
        
        return wxGzhMapper.update(wxGzhBean);
    }
}

@Service
public class SubService { 
    @Transactional
    public void updateState(WxGzhBean wxGzhBean) {
        wxGzhMapper.update(wxGzhBean);
        throw new RuntimeException("异常");
    }
}

6. 选择错误的传播行为

spring有7种事务传播行为

  1. PROPAGATION_REQUIRED 支持当前事务,假设当前没有事务。就新建一个事务
  2. PROPAGATION_SUPPORTS 支持当前事务,假设当前没有事务,就以非事务方式运行
  3. PROPAGATION_MANDATORY 支持当前事务,假设当前没有事务,就抛出异常
  4. PROPAGATION_REQUIRES_NEW 新建事务,假设当前存在事务。把当前事务挂起
  5. PROPAGATION_NOT_SUPPORTED 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起
  6. PROPAGATION_NEVER 以非事务方式运行,假设当前存在事务,则抛出异常
  7. PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则新建事务

2,5,6需要谨慎使用,会造成事务失效
目前只有三种传播属性会创建新事务:NESTED, REQUIRES_NEW, REQUIRED

NEVER 例子:

@Service
public class GzhService {     
    @Transactional 
    public Object update(WxGzhBean wxGzhBean) throws Exception {
        return subService.update(wxGzhBean);
    }
}

@Service
public class SubService { 
    @Transactional(propagation = Propagation.NEVER)
    public Object update(WxGzhBean wxGzhBean) throws Exception {
        wxGzhMapper.update(wxGzhBean);
        throw new RuntimeException("异常");
    }
}

NEVER 以非事务方式执行,如果当前存在事务,则抛出异常:Existing transaction found for transaction marked with propagation ‘never’

7. 异常未抛出

开发者在代码中手动try catch了异常,比如:

@Transactional
public void update(WxGzhBean wxGzhBean) {
    try {
        wxGzhMapper.update(wxGzhBean);
        throw new RuntimeException("异常");
    } catch (Exception e) {
        Console.log(e.getStackTrace());
    }
}

事务是通过有没有出现异常来判断是否要回滚的,因为异常被捕获住了,又没有手动抛出,spring认为程序是正常的,当然不会回滚。

8. 抛出的异常与rollbackFor的异常不符时

rollbackFor属性: 只有当出现指定异常才会回滚, 当抛出的异常与rollbackFor的异常不符时,会出现事务失效

@Transactional(rollbackFor = RuntimeException.class)
public void update(WxGzhBean wxGzhBean) throws Exception {
    wxGzhMapper.update(wxGzhBean);
    throw new Exception("异常");
}

spring事务默认情况下,只会回滚 RuntimeException(运行时异常)和 Error(错误),对于普通的 Exception (非运行时异常),它不会回滚

9. rollback-only 事务回滚

当事务嵌套时,比如事务A调用事务B,当B出现异常,事务会被标记为全局回滚,最外层事务A在commit时发现事务已打上全局回滚标签,于是先回滚事务,再抛出rollback-only 异常( transaction rolled back because it has been marked as rollback-only 事务回滚,因为它被标记为仅回滚)

@Transactional
public void a(WxGzhBean wxGzhBean) {
    try {
        gzhService.b(wxGzhBean);
    } catch (Exception e) {  } 
}

@Transactional
public void b(WxGzhBean wxGzhBean) {
    wxGzhMapper.update(wxGzhBean);
    throw new RuntimeException("异常");
}

如果想要子事务回滚不影响父事务,修改传播行为requires_new 挂起当前事务,新建一个新事务,这样两个事务互不影响

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一听可乐.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值