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方法的對象不是代理對象,可能還要看源碼,懂的人可以在評論區分享一下。

如果有錯誤,請評論區指正


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM