溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

spring事務傳播行為之使用REQUIRES_NEW不回滾

發(fā)布時間:2020-07-17 20:06:35 來源:網絡 閱讀:472 作者:沙漏半杯 欄目:編程語言

最近寫spring事務時用到REQUIRES_NEW遇到一些不回滾的問題,所以就記錄一下。


場景1:在一個服務層里面方法1和方法2都加上事務,其中方法二設置上propagation=Propagation.REQUIRES_NEW,方法1調用方法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? ? ? ? ?//售賣? 扣除庫存數量

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è)務沒什么意義,只是用來測試REQUIRES_NEW的 當執(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? ? ?}

?


三張表分別是對應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()獲取當前代理


將代碼修改為


1? ? <!-- 開啟事務注解 -->


2<tx:annotation-driven transaction-manager="transactionManager"/>


3<!-- 將代理暴露出來 -->


4<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? ? ?}

運行結果


調用的對象變成代理對象了? 那么結果可想而知第一個事務被掛起,第二個事務執(zhí)行完提交了 然后異常觸發(fā),事務一回滾? 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并且在執(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的調用者:"+((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è)務沒什么意義,只是用來測試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這個方法的事務正常執(zhí)行,所以數據庫的變化是balance字段的錢要+60? Spring這本書的庫存-1,但是結果是數據庫完全沒有變化




分析:在testUpdate方法內拋異常被spring aop捕獲,捕獲后異常又被拋出,那么異常拋出后,是不是update方法沒有手動捕獲,而是讓spring aop自動捕獲,所以在update方法內也捕獲到了異常,因此都回滾了


這張圖片的代碼是我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的調用者:"+((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í)行結果? ? update執(zhí)行成功? ?testUpdate回滾


?總結:同一個Service不同事務的嵌套會出現調用的對象不是代理對象的問題,如果是多個不同Service的不同事務嵌套就沒有這個問題。場景2的要記得手動捕獲異常,不然全回滾了.至于為什么調用testUpdate方法的對象不是代理對象,可能還要看源碼,懂的人可以在評論區(qū)分享一下。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI