最近寫spring事務(wù)時用到REQUIRES_NEW遇到一些不回滾的問題,所以就記錄一下。
成都創(chuàng)新互聯(lián)專注于資溪企業(yè)網(wǎng)站建設(shè),成都響應(yīng)式網(wǎng)站建設(shè)公司,商城網(wǎng)站建設(shè)。資溪網(wǎng)站建設(shè)公司,為資溪等地區(qū)提供建站服務(wù)。全流程按需定制開發(fā),專業(yè)設(shè)計,全程項目跟蹤,成都創(chuàng)新互聯(lián)專業(yè)和態(tài)度為您提供的服務(wù)
場景1:在一個服務(wù)層里面方法1和方法2都加上事務(wù),其中方法二設(shè)置上propagation=Propagation.REQUIRES_NEW,方法1調(diào)用方法2并且在執(zhí)行完方法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? ? ? ? ?//售賣? 扣除庫存數(shù)量
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? ? ? ? ?//這個業(yè)務(wù)沒什么意義,只是用來測試REQUIRES_NEW的 當(dāng)執(zhí)行后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? ? ?}
?
三張表分別是對應(yīng)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? ? ?}
結(jié)果是無論是方法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的調(diào)用者:"+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的調(diào)用者:"+this.getClass());
12? ? ? ? ?testUpdate();
13? ? ? ? ?
14? ? ? ? ?jdbcTemplate.update(addRmbSql, params);
15? ? ? ? ?
16? ? ? ? ?throw new RuntimeException("故意的一個異常");
17? ? ?}
運行結(jié)果是
顯然調(diào)用update的對象是一個代理對象,調(diào)用testUpdate的對象不是一個代理對象,這就是為什么添加REQUIRES_NEW不起作用,想要讓注解生效就要用代理對象的方法,不能用原生對象的.
解決方法:在配置文件中添加標(biāo)簽
將代碼修改為
1? ?
2
3
4
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的調(diào)用者:"+((BookService)AopContext.currentProxy()).getClass());
12? ? ? ? ?((BookService)AopContext.currentProxy()).testUpdate();
13? ? ? ? ?
14? ? ? ? ?jdbcTemplate.update(addRmbSql, params);
15? ? ? ? ?
16? ? ? ? ?throw new RuntimeException("故意的一個異常");
17? ? ?}
運行結(jié)果
調(diào)用的對象變成代理對象了? 那么結(jié)果可想而知第一個事務(wù)被掛起,第二個事務(wù)執(zhí)行完提交了 然后異常觸發(fā),事務(wù)一回滾? SpringMVC這本書庫存-1,其他的不變
我還看到過另一種解決方法??
在第7行加一個BookService類型的屬性并且給個set方法,目的就是將代理對象傳遞過來...? ? 看26 27行顯然就是用代理對象去調(diào)用的方法? ?所以就解決問題了? ?不過還是用第一個方案好
?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的調(diào)用者:"+((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:在一個服務(wù)層里面方法1和方法2都加上事務(wù),其中方法二設(shè)置上propagation=Propagation.REQUIRES_NEW,方法1調(diào)用方法2并且在執(zhí)行方法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的調(diào)用者:"+((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? ? ? ? ?//這個業(yè)務(wù)沒什么意義,只是用來測試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? ? ?}
預(yù)期結(jié)果是testUpdate這個事務(wù)是要回滾的,update這個方法的事務(wù)正常執(zhí)行,所以數(shù)據(jù)庫的變化是balance字段的錢要+60? Spring這本書的庫存-1,但是結(jié)果是數(shù)據(jù)庫完全沒有變化
分析:在testUpdate方法內(nèi)拋異常被spring aop捕獲,捕獲后異常又被拋出,那么異常拋出后,是不是update方法沒有手動捕獲,而是讓spring aop自動捕獲,所以在update方法內(nèi)也捕獲到了異常,因此都回滾了
這張圖片的代碼是我debug模式下? 在testUpdate方法中執(zhí)行到拋出異常的地方? 再點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的調(diào)用者:"+((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? ? ?}
執(zhí)行結(jié)果? ? update執(zhí)行成功? ?testUpdate回滾
?總結(jié):同一個Service不同事務(wù)的嵌套會出現(xiàn)調(diào)用的對象不是代理對象的問題,如果是多個不同Service的不同事務(wù)嵌套就沒有這個問題。場景2的要記得手動捕獲異常,不然全回滾了.至于為什么調(diào)用testUpdate方法的對象不是代理對象,可能還要看源碼,懂的人可以在評論區(qū)分享一下。