spring事务传播行为之使用REQUIRES_NEW不回滚
Posted 酒鬼_blog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring事务传播行为之使用REQUIRES_NEW不回滚相关的知识,希望对你有一定的参考价值。
最近写spring事务时用到REQUIRES_NEW遇到一些不回滚的问题,所以就记录一下。
场景1:在一个服务层里面方法1和方法2都加上事务,其中方法二设置上propagation=Propagation.REQUIRES_NEW,方法1调用方法2并且在执行完方法2后抛出一个异常,如下代码
1 @Service 2 public class BookServiceImpl implements BookService { 3 4 @Autowired 5 private JdbcTemplate jdbcTemplate; 6 7 @Transactional(timeout=4) 8 public void update() { 9 // TODO Auto-generated method stub 10 //售卖 扣除库存数量 11 String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)"; 12 //入账的sql 将赚到的钱添加到account表中的balance 13 String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)"; 14 Object []params = {1,"Spring"}; 15 16 jdbcTemplate.update(sellSql, params); 17 18 testUpdate(); 19 20 jdbcTemplate.update(addRmbSql, params); 21 22 throw new RuntimeException("故意的一个异常"); 23 } 24 @Transactional(propagation=Propagation.REQUIRES_NEW) 25 public void testUpdate() { 26 //这个业务没什么意义,只是用来测试REQUIRES_NEW的 当执行后SpringMVC这本书库存-1 27 String sql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)"; 28 Object []params = {1,"SpringMVC"}; 29 jdbcTemplate.update(sql, params); 30 31 }
三张表分别是对应account表,book表,book_stock表
1 private static ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/*.xml"); 2 3 @Test 4 public void testREQUIRES_NEW() { 5 6 BookService bean = ac.getBean(BookService.class); 7 8 bean.update(); 9 }
结果是无论是方法1还是方法2都回滚了,那么REQUIRES_NEW就不起作用了,为了探索原因我修改了一下代码
在第5行的地方打印出对象的类型是什么
1 @Test 2 public void testREQUIRES_NEW() { 3 4 BookService bean = ac.getBean(BookService.class); 5 System.out.println("update的调用者:"+bean.getClass()); 6 bean.update(); 7 }
在第11行的地方打印对象类型
1 @Transactional(timeout=4) 2 public void update() { 3 // TODO Auto-generated method stub 4 //售卖 5 String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)"; 6 //入账的sql 7 String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)"; 8 Object []params = {1,"Spring"}; 9 10 jdbcTemplate.update(sellSql, params); 11 System.out.println("testUpdate的调用者:"+this.getClass()); 12 testUpdate(); 13 14 jdbcTemplate.update(addRmbSql, params); 15 16 throw new RuntimeException("故意的一个异常"); 17 }
运行结果是
显然调用update的对象是一个代理对象,调用testUpdate的对象不是一个代理对象,这就是为什么添加REQUIRES_NEW不起作用,想要让注解生效就要用代理对象的方法,不能用原生对象的.
解决方法:在配置文件中添加标签<aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>将代理暴露出来,使AopContext.currentProxy()获取当前代理
将代码修改为
<!-- 开启事务注解 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- 将代理暴露出来 --> <aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>
11 12行将this替换为((BookService)AopContext.currentProxy())
1 @Transactional(timeout=4) 2 public void update() { 3 // TODO Auto-generated method stub 4 //售卖 5 String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)"; 6 //入账的sql 7 String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)"; 8 Object []params = {1,"Spring"}; 9 10 jdbcTemplate.update(sellSql, params); 11 System.out.println("testUpdate的调用者:"+((BookService)AopContext.currentProxy()).getClass()); 12 ((BookService)AopContext.currentProxy()).testUpdate(); 13 14 jdbcTemplate.update(addRmbSql, params); 15 16 throw new RuntimeException("故意的一个异常"); 17 }
运行结果
调用的对象变成代理对象了 那么结果可想而知第一个事务被挂起,第二个事务执行完提交了 然后异常触发,事务一回滚 SpringMVC这本书库存-1,其他的不变
我还看到过另一种解决方法
在第7行加一个BookService类型的属性并且给个set方法,目的就是将代理对象传递过来... 看26 27行显然就是用代理对象去调用的方法 所以就解决问题了 不过还是用第一个方案好
1 @Service 2 public class BookServiceImpl implements BookService { 3 4 @Autowired 5 private JdbcTemplate jdbcTemplate; 6 7 private BookService proxy; 8 9 public void setProxy(BookService proxy) { 10 this.proxy = proxy; 11 } 12 13 @Transactional(timeout=4) 14 public void update() { 15 // TODO Auto-generated method stub 16 //售卖 17 String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)"; 18 //入账的sql 19 String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)"; 20 Object []params = {1,"Spring"}; 21 22 jdbcTemplate.update(sellSql, params); 23 /* System.out.println("testUpdate的调用者:"+((BookService)AopContext.currentProxy()).getClass()); 24 ((BookService)AopContext.currentProxy()).testUpdate();*/ 25 26 System.out.println(proxy.getClass()); 27 proxy.testUpdate(); 28 29 jdbcTemplate.update(addRmbSql, params); 30 31 throw new RuntimeException("故意的一个异常"); 32 }
OK这个问题解决那就下一个
场景2:在一个服务层里面方法1和方法2都加上事务,其中方法二设置上propagation=Propagation.REQUIRES_NEW,方法1调用方法2并且在执行方法2时抛出一个异常 没注意看是不是觉得两个场景是一样的,因为我是拷贝下来改的... 差别就是在哪里抛出异常 这次是在方法2里面抛出异常, 我将代码还原至场景1的第一个解决方案,然后在方法2里面抛出异常 代码如下
1 @Service 2 public class BookServiceImpl implements BookService { 3 4 @Autowired 5 private JdbcTemplate jdbcTemplate; 6 7 @Transactional(timeout=4) 8 public void update() { 9 // TODO Auto-generated method stub 10 //售卖 11 String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)"; 12 //入账的sql 13 String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)"; 14 Object []params = {1,"Spring"}; 15 16 jdbcTemplate.update(sellSql, params); 17 18 System.out.println("testUpdate的调用者:"+((BookService)AopContext.currentProxy()).getClass()); 19 ((BookService)AopContext.currentProxy()).testUpdate(); 20 21 jdbcTemplate.update(addRmbSql, params); 22 23 } 24 @Transactional(propagation=Propagation.REQUIRES_NEW) 25 public void testUpdate() { 26 //这个业务没什么意义,只是用来测试REQUIRES_NEW的 27 String sql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)"; 28 Object []params = {1,"SpringMVC"}; 29 jdbcTemplate.update(sql, params); 30 31 throw new RuntimeException("在方法二里面抛出一个异常"); 32 }
预期结果是testUpdate这个事务是要回滚的,update这个方法的事务正常执行,所以数据库的变化是balance字段的钱要+60 Spring这本书的库存-1,但是结果是数据库完全没有变化
分析:在testUpdate方法内抛异常被spring aop捕获,捕获后异常又被抛出,那么异常抛出后,是不是update方法没有手动捕获,而是让spring aop自动捕获,所以在update方法内也捕获到了异常,因此都回滚了
这张图片的代码是我debug模式下 在testUpdate方法中执行到抛出异常的地方 再点step over 跳到的地方 显然spring aop捕获到了异常后,再次抛出,这就是为什么update方法会捕获到异常
OK问题很简单 解决方案也很简单 只需要手动捕获该异常,不让spring aop捕获就OK了
将update方法改为
1 @Transactional(timeout=4) 2 public void update() { 3 // TODO Auto-generated method stub 4 //售卖 5 String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)"; 6 //入账的sql 7 String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)"; 8 Object []params = {1,"Spring"}; 9 10 jdbcTemplate.update(sellSql, params); 11 12 try { 13 System.out.println("testUpdate的调用者:"+((BookService)AopContext.currentProxy()).getClass()); 14 ((BookService)AopContext.currentProxy()).testUpdate(); 15 } catch (RuntimeException e) { 16 // TODO Auto-generated catch block 17 System.out.println(e.getMessage()); 18 e.printStackTrace(); 19 } 20 21 jdbcTemplate.update(addRmbSql, params); 22 23 }
执行结果 update执行成功 testUpdate回滚
总结:同一个Service不同事务的嵌套会出现调用的对象不是代理对象的问题,如果是多个不同Service的不同事务嵌套就没有这个问题。场景2的要记得手动捕获异常,不然全回滚了.至于为什么调用testUpdate方法的对象不是代理对象,可能还要看源码,懂的人可以在评论区分享一下。
如果有错误,请评论区指正
以上是关于spring事务传播行为之使用REQUIRES_NEW不回滚的主要内容,如果未能解决你的问题,请参考以下文章