Spring 事务失效的场景

1、private、final、static 方法

@Transactional 注解标注的方法的访问权限必须是 public;

@Transactional 注解标注的方法不能被 final、static 修饰,被标注的方法必须是可覆盖的。这是因为事务底层使用的是 aop,而 aop 使用的是代理模式。代理模式生成的代理类无法重写被 final、static 修饰的方法。而 private 方法对子类不可见。

2、非事务方法调用

非事务方法调用事务方法,事务方法会失效。

public void transfer() {
    String sql = "update `test`  set money = money + 100 where id = 1;";
    jdbcTemplate.update(sql);
    reduce();
}

@Transactional
public void reduce() {
    String sql = "update `test`  set money = money - 100 where id = 2;";
    jdbcTemplate.update(sql);
    int i = 1 / 0;
}

这种情况两个方法的操作都不会进行回滚。reduce() 方法相当于 this.reduce(),而 this 不是代理对象,所以 reduce 方法事务失效。

解决方案也有几种,比如:将事务方法移动到另外一个类中、在本类中注入自己、使用 @EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy()

这里使用第二种方式。

@Autowired
private TestServiceImpl serviceImpl;

public void transfer() {
    String sql = "update `test`  set money = money + 100 where id = 1;";
    jdbcTemplate.update(sql);
    serviceImpl.reduce();
}

@Transactional
public void reduce() {
    String sql = "update `test`  set money = money - 100 where id = 2;";
    jdbcTemplate.update(sql);
    int i = 1 / 0;
}

这样 reduce() 方法就不会事务失效,所以发生异常会进行回滚。但 transfer 就不是个事务方法,所以不会回滚。

3、将异常处理掉

 @Transactional
 public void transfer() {
     String sql = "update `test`  set money = money + 100 where id = 1;";
     jdbcTemplate.update(sql);
     //serviceImpl.reduce();
     try {
         int i = 1 /0;
     } catch (Exception e) {
        
     }
 }

4、抛出的异常不在回滚范围内

 @Transactional
 public void transfer() throws Exception {
     String sql = "update `test`  set money = money + 100 where id = 1;";
     jdbcTemplate.update(sql);
     //serviceImpl.reduce();
     try {
         int i = 1 /0;
     } catch (Exception e) {
         throw new Exception(e);
     }
 }

默认情况下,Spring 事务只有遇到 RuntimeException 以及 Error 时才会回滚,在遇到检查型异常时是不会回滚的,比如 IOException、TimeoutException。所以,一般情况下都需要使用 rollbackFor参数指定回滚异常类,比如:@Transactional(rollbackFor = Exception.class)

5、使用错误的传播行为

@Transactional(rollbackFor = Exception.class)
public void transfer() {
    String sql = "update `test`  set money = money + 100 where id = 1;";
    jdbcTemplate.update(sql);
    serviceImpl.reduce();
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void reduce() {
    String sql = "update `test`  set money = money - 100 where id = 2;";
    jdbcTemplate.update(sql);
    int i = 1 / 0;
}

这种写法会使 reduce 方法事务失效,出现异常不会回滚。这是因为使用了 NOT_SUPPORTED 的传播行为,该行为的特性是:以非事务方式运行,如果当前存在事务,则把当前事务挂起。而 transfer 方法会进行事务回滚,这是因为 reduce 方法的异常会往上抛,被 transfer 感知到,进行了事务回滚。

6、多线程调用

@Transactional(rollbackFor = Exception.class)
public void transfer() throws InterruptedException {
    String sql = "update `test`  set money = money + 100 where id = 1;";
    jdbcTemplate.update(sql);
    new Thread(() ->{
        serviceImpl.reduce(jdbcTemplate);
    }).start();
    Thread.sleep(1000);
}

@Transactional(rollbackFor = Exception.class)
public void reduce(JdbcTemplate jdbcTemplate) {
    String sql = "update `test`  set money = money - 100 where id = 2;";
    jdbcTemplate.update(sql);
    int i = 1 / 0;
}

从示例代码中,可以看到事务方法 transfer 调用了事务方法 reduce,而 reduce 方法是开启了一个新线程调用的。这样会导致 reduce 方法不会加入到 transfer 事务中,reduce 方法会重新创建一个新事务。 这是因为 Spring 的事务是通过数据库连接来创建的,同一个事务,只能用同一个数据库连接。而多线程场景下,拿到的数据库连接是不一样的,即会导致获取到的不同事务。既然是两个事务,则没办法进行统一回滚。

7、数据库引擎不支持事务

比如 Mysql 的 MyISAM引擎就不支持事务。

8、代理类过早实例化

@Service
public class TestServiceImpl implements BeanPostProcessor, Ordered {

    @Autowired
    private TestServiceImpl serviceImpl;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional(rollbackFor = Exception.class)
    public void transfer()  {
        String sql = "update `test`  set money = money + 100 where id = 1;";
        jdbcTemplate.update(sql);
        serviceImpl.reduce(jdbcTemplate);
    }

    private void reduce(JdbcTemplate jdbcTemplate) {
        String sql = "update `test`  set money = money - 100 where id = 2;";
        jdbcTemplate.update(sql);
        int i = 1 / 0;
    }


    @Override
    public int getOrder() {
        return 1;
    }
}

当代理类的实例化早于 AbstractAutoProxyCreator后置处理器,就无法被AbstractAutoProxyCreator后置处理器进行AOP增强。

上面 8 种事务失效场景中,需要我们平常注意的只有 2、3、4、5。

#java后端##程序员#
全部评论

相关推荐

老粉都知道小猪猪我很久没更新了,因为秋招非常非常不顺利,emo了三个月了,接下来说一下我的情况吧本人是双非本 专业是完全不着计算机边的非科班,比较有优势的是有两段大厂实习,美团和字节。秋招面了50+场泡池子泡死的:滴滴 快手 去哪儿 小鹏汽车 不知名的一两个小厂其中字节13场 两次3面挂 两次2面挂 一次一面挂其中有2场面试题没写出来,其他的都是全a,但该挂还是挂,第三次三面才面进去字节,秋招加暑期总共面了22次字节,在字节的面评可以出成书了快手面了8场,2次实习的,通过了但没去,一次2面挂 最后一次到录用评估 至今无消息滴滴三面完 没几天挂了 所有技术面找不出2个问题是我回答不上来的,三面还来说我去过字节,应该不会考虑滴滴吧,直接给我干傻了去哪儿一天速通 至今无消息小鹏汽车hr 至今无消息美团2面挂 然后不捞我了,三个志愿全部结束,估计被卡学历了虾皮二面挂 这个是我菜,面试官太牛逼了拼多多二面挂 3道题也全写了 也没问题是回答不出来的 泡一周后挂腾讯面了5次 一次2面挂 三次一面挂,我宣布腾讯是世界上最难进的互联网公司然后还有一些零零散散的中小厂,但是数量比较少,约面大多数都是大厂。整体的战况非常惨烈,面试机会少,就算面过了也需要和各路神仙横向对比,很多次我都是那个被比下去的人,不过这也正常,毕竟谁会放着一个985的硕士不招,反而去招一个双非读化学的小子感觉现在互联网对学历的要求越来越高了,不仅仅要985还要硕士了,双非几乎没啥生存空间了,我感觉未来几年双非想要进大厂开发的难度应该直线上升了,唯一的打法还是从大二刷实习,然后苟个转正,不然要是去秋招大概率是炮灰。而且就我面字节这么多次,已经开始问很多ai的东西了,你一破本科生要是没实习没科研懂什么ai啊,纯纯白给了
不知名牛友_:爸爸
秋招你被哪家公司挂了?
点赞 评论 收藏
分享
评论
1
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务