发现一个Spring事务的巨坑bug 你必须要小心了

1.错误的访问权限
2.方法被定义成final的
3.方法内部调用
4.当前实体没有被spring管理
5.错误的spring事务传播特性
6.数据库不支持事务
7.自己吞掉了异常
8.抛出的异常不正确
9.多线程调用
10.嵌套事务多回滚了
对于从事java开发工作的同学来说,spring的事务肯定再熟悉不过了。在某些业务场景下,如果同时有多张表的写入操作,为了保证操作的原子性(要么同时成功,要么同时失败)避免数据不一致的情况,我们一般都会使用spring事务。
没错,spring事务大多数情况下,可以满足我们的业务需求。但是今天我要告诉大家的是,它有很多坑,稍不注意事务就会失效。
不信,我们一起看看。
1.错误的访问权限
@servicepublic class userservice {    @autowired    private usermapper usermapper;        @transactional    private void add(usermodel usermodel) {        usermapper.insertuser(usermodel);    }}  
我们可以看到add方法的访问权限被定义成了private,这样会导致事务失效,spring要求被代理方法必须是public的。
abstractfallbacktransactionattributesource类的computetransactionattribute方法中有个判断,如果目标方法不是public,则transactionattribute返回null,即不支持事务。
protected transactionattribute computetransactionattribute(method method, @nullable class targetclass) {    // don't allow no-public methods as required.    if (allowpublicmethodsonly() && !modifier.ispublic(method.getmodifiers())) {      return null;    }    // the method may be on an interface, but we need attributes from the target class.    // if the target class is null, the method will be unchanged.    method specificmethod = aoputils.getmostspecificmethod(method, targetclass);    // first try is the method in the target class.    transactionattribute txattr = findtransactionattribute(specificmethod);    if (txattr != null) {      return txattr;    }    // second try is the transaction attribute on the target class.    txattr = findtransactionattribute(specificmethod.getdeclaringclass());    if (txattr != null && classutils.isuserlevelmethod(method)) {      return txattr;    }    if (specificmethod != method) {      // fallback is to look at the original method.      txattr = findtransactionattribute(method);      if (txattr != null) {        return txattr;      }      // last fallback is the class of the original method.      txattr = findtransactionattribute(method.getdeclaringclass());      if (txattr != null && classutils.isuserlevelmethod(method)) {        return txattr;      }    }    return null;  }  
2.方法被定义成final的
@servicepublic class userservice {    @autowired    private usermapper usermapper;    @transactional    public final void add(usermodel usermodel) {        usermapper.insertuser(usermodel);    }}  
我们可以看到add方法被定义成了final的,这样会导致spring aop生成的代理对象不能复写该方法,而让事务失效。
3.方法内部调用
@servicepublic class userservice {    @autowired    private usermapper usermapper;    @transactional    public void add(usermodel usermodel) {        usermapper.insertuser(usermodel);        updatestatus(usermodel);    }    @transactional    public void updatestatus(usermodel usermodel) {        // dosamething();    }}  
我们看到在事务方法add中,直接调用事务方法updatestatus。从前面介绍的内容可以知道,updatestatus方法拥有事务的能力是因为spring aop生成代理了对象,但是这种方法直接调用了this对象的方法,所以updatestatus方法不会生成事务。
4.当前实体没有被spring管理
//@servicepublic class userservice {    @autowired    private usermapper usermapper;    @transactional    public void add(usermodel usermodel) {        usermapper.insertuser(usermodel);    }}  
我们可以看到userservice类没有定义@service注解,即没有交给spring管理bean实例,所以它的add方法也不会生成事务。
5.错误的spring事务传播特性
@servicepublic class userservice {    @autowired    private usermapper usermapper;    @transactional(propagation = propagation.never)    public void add(usermodel usermodel) {        usermapper.insertuser(usermodel);    }}  
我们可以看到add方法的事务传播特性定义成了propagation.never,这种类型的传播特性不支持事务,如果有事务则会抛异常。只有这三种传播特性才会创建新事务:propagation_required,propagation_requires_new,propagation_nested。
6.数据库不支持事务
msql8以前的版本数据库引擎是支持myslam和innerdb的。我以前也用过,对应查多写少的单表操作,可能会把表的数据库引擎定义成myslam,这样可以提升查询效率。但是,要千万记得一件事情,myslam只支持表锁,并且不支持事务。所以,对这类表的写入操作事务会失效。
7.自己吞掉了异常
@slf4j@servicepublic class userservice {    @autowired    private usermapper usermapper;        @transactional    public void add(usermodel usermodel) {        try {            usermapper.insertuser(usermodel);        } catch (exception e) {            log.error(e.getmessage(), e);        }    }}  
这种情况下事务不会回滚,因为开发者自己捕获了异常,又没有抛出。事务的aop无法捕获异常,导致即使出现了异常,事务也不会回滚。
8.抛出的异常不正确
@slf4j@servicepublic class userservice {    @autowired    private usermapper usermapper;        @transactional    public void add(usermodel usermodel) throws exception {        try {            usermapper.insertuser(usermodel);        } catch (exception e) {            log.error(e.getmessage(), e);            throw new exception(e);        }    }}  
这种情况下,开发人员自己捕获了异常,又抛出了异常:exception,事务也不会回滚。因为spring事务,默认情况下只会回滚runtimeexception(运行时异常)和error(错误),不会回滚exception。
9.多线程调用
@slf4j@servicepublic class userservice {    @autowired    private usermapper usermapper;    @autowired    private roleservice roleservice;    @transactional    public void add(usermodel usermodel) throws exception {        usermapper.insertuser(usermodel);        new thread(() -> {            roleservice.dootherthing();        }).start();    }}@servicepublic class roleservice {    @transactional    public void dootherthing() {        system.out.println(保存role表数据);    }}  
我们可以看到事务方法add中,调用了事务方法dootherthing,但是事务方法dootherthing是在另外一个线程中调用的,这样会导致两个事务方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想dootherthing方法中抛了异常,add方法也回滚是不可能的。
如果看过spring事务源码的朋友,可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。
private static final threadlocal resources =      new namedthreadlocal(transactional resources);  
我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。
10.嵌套事务多回滚了
public class userservice {    @autowired    private usermapper usermapper;    @autowired    private roleservice roleservice;    @transactional    public void add(usermodel usermodel) throws exception {        usermapper.insertuser(usermodel);        roleservice.dootherthing();    }}@servicepublic class roleservice {    @transactional(propagation = propagation.nested)    public void dootherthing() {        system.out.println(保存role表数据);    }}  
这种情况使用了嵌套的内部事务,原本是希望调用roleservice.dootherthing方法时,如果出现了异常,只回滚dootherthing方法里的内容,不回滚 usermapper.insertuser里的内容,即回滚保存点。。但事实是,insertuser也回滚了。
why?
因为dootherthing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。
怎么样才能只回滚保存点呢?
@slf4j@servicepublic class userservice {    @autowired    private usermapper usermapper;    @autowired    private roleservice roleservice;    @transactional    public void add(usermodel usermodel) throws exception {        usermapper.insertuser(usermodel);        try {            roleservice.dootherthing();        } catch (exception e) {            log.error(e.getmessage(), e);        }    }}  
在代码中手动把内部嵌套事务放在try/catch中,并且不继续往抛异常。
介绍到这里,你会发现spring事务的坑还是挺多的~

iphone历史版本你拥有过哪些?最值得期待的是iPhone 8?
如何正确合理的使用水浸传感器来监测机房漏水情况
基于频偏功能的混频器/变频器一致性测量解析
Oppo推出了Find X2和Find X2 Pro 5G智能手机
PyTorch核心华人开发者透彻解读PyTorch内部机制
发现一个Spring事务的巨坑bug 你必须要小心了
uCloudlink 推出创新移动数据服务 “GlocalMe® Inside”
低价竞争致业绩大幅缩水 华为/OPPO供应商同兴达能否脱困?
浪涌防雷器的工作原理是什么
还在等iPhone8?华为Mate10、小米Note3、小米MIX2、魅族Pro7即将发布毫不逊色!
王晓鹏用6年追求的顶尖国产虹膜识别技术怎么样了
为什么说云计算对中国未来的IC设计产业至关重要?
生产物流液晶电子看板走势
如何让自已调频接收出靓声
协作机器人在3C电子行业的典型应用场景展示
银行高清可视化火灾预警方案的结构组成和特点分析
板带表面缺陷检测系统助力实现高效精确智能化
Wrap 函数:绝对坐标中旋转分度的简化位置控制
地球为什么要流浪?(芯片角度分析)
升级版华为MateX或在10月份发布 搭载麒麟990并支持5G网络