事务不生效的几种case
所有的测试Case,测试完毕后,DB数据需要手动更新成原始数据,保证测试Case互不影响。
Case1: 类内部访问
简单来讲就是指非直接访问带注解标记的方法 B,而是通过类普通方法 A,然后由 A 访问 B,下面是一个简单的 case,我们在类UserController中新增一个方法testFail():
public void testFail() throws Exception { testSuccess(); throw new Exception("测试事务回滚不生效"); }
这里我们是通过testFail()调用testSuccess(),再看一下测试用例:
public static void main(String[] args) throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserController uc = (UserController) applicationContext.getBean("userController"); try { uc.testFail(); } finally { MyUser user = uc.query(1); System.out.println("修改后的记录:" + user); } } // 输出: // 原记录:User[uid=1,uname=张三,usex=女] // 修改后的记录:User[uid=1,uname=张三,usex=女]
从上面的输出可以看到,事务并没有回滚,主要是因为类内部调用,不会通过代理方式访问。
Case2: 私有方法
在私有方法上,添加@Transactional注解也不会生效,私有方法外部不能访问,所以只能内部访问,上面的 case 不生效,这个当然也不生效了:
@Transactional(rollbackFor = Exception.class) private void testSuccess() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原记录:" + user); update(id); throw new Exception("测试事务生效"); }
直接使用时,下面这种场景不太容易出现,因为 IDEA 会有提醒,文案为: Methods annotated with '@Transactional' must be overridable
Case3: 异常不匹配
@Transactional注解默认处理运行时异常,即只有抛出运行时异常时,才会触发事务回滚,否则并不会回滚:
@Transactional public void testSuccess() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原记录:" + user); update(id); throw new Exception("测试事务生效"); }
测试 case 如下:
public static void main(String[] args) throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserController uc = (UserController) applicationContext.getBean("userController"); try { uc.testSuccess(); } finally { MyUser user = uc.query(1); System.out.println("修改后的记录:" + user); } } // 输出: // 原记录:User[uid=1,uname=张三,usex=女] // 修改后的记录:User[uid=1,uname=张三-test,usex=女]
输出结果如下,事务并未回滚(如果需要解决这个问题,通过设置@Transactional的 rollbackFor 属性即可)
Case4: 多线程
这个场景可能并不多见,在标记事务的方法内部,另起子线程执行 db 操作,此时事务同样不会生效
下面给出两个不同的姿势,一个是子线程抛异常,主线程 ok;一个是子线程 ok,主线程抛异常。
父线程抛出异常
我们在类UserController中新增一个方法testMultThread(),该方法在主线程会抛出异常:
public void testSuccess() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原记录:" + user); update(id); } @Transactional(rollbackFor = Exception.class) public void testMultThread() throws Exception { new Thread(new Runnable() { @SneakyThrows @Override public void run() { testSuccess(); } }).start(); throw new Exception("测试事务不生效"); }
上面这种场景不生效很好理解,子线程的异常不会被外部的线程捕获,testMultThread这个方法的调用不抛异常,因此不会触发事务回滚,调用方式和前面一样,就换一个方法:
uc.testMultThread();
这里提醒一下,输出的数据因为都是在主线程中输出,所以输出结果都是“张三”,后来我看了库表,发现DB数据已经更新为“张三-testing”,所以事务回滚没有生效。
子线程抛出异常
我们修改代码如下,让子任务抛出异常:
public void testSuccess() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原记录:" + user); update(id); throw new Exception("测试事务生效"); } @Transactional(rollbackFor = Exception.class) public void testMultThread() throws Exception { new Thread(new Runnable() { @SneakyThrows @Override public void run() { testSuccess(); } }).start(); }
同上,DB数据没有回滚,发现DB数据已经更新为“张三-testing”,所以事务回滚没有生效。
Case5: 传播属性
这个内容,我后面再单独出一篇文章来讲,要不然这篇文章篇幅又太长了。
后记
感觉@Transactional这个注解需要了解的内容稍微有点多,不过这个注解是我们绕不开的,所以需要大家好好学习,特别是事务不生效的坑!其中有一个坑“类内部访问”,我刚开始写JAVA是就踩过,后来还是同事告诉我的,现在回顾当时的状态,感觉真的是菜的不能再菜了。
文章后面遗留了“传播属性”的坑,这个我就先放一下,等后面有时间我再写,然后关于Spring的部分,还剩下AOP,这几天我会先把相关知识看完,然后再通过文章的形式分享出来,敬请期待...