1. 程式人生 > >SpringBoot+Mybatis事務管理

SpringBoot+Mybatis事務管理

一、使用場景

  在使用事務管理前,有必要先了解下應用場景。如實際過程中一個購買操作包含多個執行過程:查詢庫存、下單、更新庫存,實際操作時,由於高併發存在,可能到下單結束時,更新庫存出錯,那本次購買操作就是失敗的,其下單結果應該被回滾。這種情況就需要引入事務控制,保證整個操作的有效性。

       工作場景:兩個患者賬戶之間轉賬,一個介面中操作,轉出方扣錢,插入轉出方流水;轉入方加錢,插入轉入方流水;涉及到2個人的轉賬,2個update賬戶方法必須在同一個事務中,否則可能發生轉賬會丟失錢或缺流水,即轉出方減錢但轉入方並沒有加錢或者缺流水。如果其中某個操作失敗了,整個轉賬要被回滾,轉出轉入及插入轉出轉入流水在一個事務中,保證4個操作作為一個整體,一致,原子,持久。

          涉及到業務:入參必填,轉出方和轉入方賬戶是否存在,錢的金額格式為正數,錢不能大於轉出方賬戶餘額,不能自己給自己轉。

二、配置方法

(1)配置applicationContext.xml

  1. <!-- 事務管理器 -->

  2. <bean id="txManager"

  3. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

  4. <property name="dataSource" ref="dataSource" />

  5. </bean>

  6. <!-- 事務註解驅動,標註@Transactional的類和方法將具有事務性 -->

  7. <tx:annotation-driven transaction-manager="txManager"/>

(2)給具體業務方法添加註解

  1. public class TestService {

  2. @Transactional("txManager")

  3. public void buy(){

  4. AuthenticationMapper authenticationMapper = MyApplicationContextUtil.getBean("authenticationMapper");

  5. int item_id = 2;

  6. // 查詢指定書目狀態

  7. Integer status = authenticationMapper.selectStatusOfItem(item_id);

  8. if (status == 1) {

  9. Integer count = authenticationMapper.insertOrder(item_id);

  10. if (count == 1) {

  11. authenticationMapper.updateStatus(item_id);

  12. throw new IllegalArgumentException("資料已存在,回滾");

  13. }

  14. }

  15. }

  16. }

  接下來,對buy方法做一個說明,該方法實現如下功能:

  • 1.書籍狀態查詢(select)。
  • 2.可購買狀態時,建立訂單(insert)。
  • 3.更新資料狀態為不可購買(update)。

  這個過程不加事務控制時,可能會出現一個問題,訂單建立後,資料更新失敗,導致這個過程執行失敗,就會給資料庫帶來髒資料,這些訂單資料也沒有意義。所以這裡通過事務控制來保證方法執行的一致性,過程中失敗則回滾。

  當然,Spring中事務回滾是有觸發機制的,其觸發機制就是丟擲unchecked異常,即RuntimeException及其子類異常。即上述程式碼中的實現:

  1. throws Exception// 方法上直接丟擲即可回滾

  2. throw new RuntimeException("資料已存在,回滾");

  3. catch中使用TransactionAscpectSupport.currentTransactionStatus().setRollbackOnly().//手動回滾並給頁面提供友好提示 return new Result<>("1","優惠券核銷異常",null).

  但是,如果是丟擲checked異常,即需要在程式碼中顯式地處理,比如try-catch塊處理,或者給所在的方法加上throws說明。對於這類異常,也要實現回滾。既然已知其觸發機制,人為製造觸發點即可,比如在catch中丟擲unchecked異常。

  1. try {

  2. throw new IOException("IO異常");

  3. } catch (IOException e) {

  4. e.printStackTrace();

  5. throw new RuntimeException("資料已存在,回滾");

  6. }

  有關checked與unchecked異常區別後續補充,本文不展開。有關丟擲RuntimeException,其實自己可以定義一個異常類來處理。

  1. public class MyException extends RuntimeException{

  2. public MyException(String message) {

  3. super(message);

  4. }

  5. }

(3)業務類注入bean

  在beans.xml中新增testService的bean,交由Spring來統一管理業務物件,這樣該物件上面的事務才會生效。

 <bean id="testService" class="com.loongshawn.service.TestService"/>

  如果業務物件是通過new產生的,即testService沒有註冊bean,資料庫是不會執行回滾的,即如下:

  1. TestService testService = new TestService();

  2. testService.buy();

(4)業務測試

  1. public void executeDeliveryTask() {

  2. TestService testService = MyApplicationContextUtil.getBean("testService");

  3. testService.buy();

  4. }

執行結果:

這裡寫圖片描述

資料庫中沒有髒資料插入。

三、事務屬性

spring事務有7種傳播行為,分別是:

  • 1、PROPAGATION.REQUIRED:如果當前沒有事務,就建立一個新事務,如果當前存在事務,就加入該事務,該設定是最常用的設定。

  • 2、PROPAGATION.SUPPORTS:支援當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行。

  • 3、PROPAGATION.MANDATORY:支援當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就丟擲異常。

  • 4、PROPAGATION.REQUIRES_NEW:建立新事務,無論當前存不存在事務,都建立新事務。

  • 5、PROPAGATION.NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。

  • 6、PROPAGATION.NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。

  • 7、PROPAGATION.NESTED:如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。

  註解@Transactional預設的傳播行為是:PROPAGATION.REQUIRED

四、注意事項

  • 1、在需要事務管理的地方加@Transactional 註解。@Transactional 註解可以被應用於介面定義和介面方法、類定義和類的 public 方法上。

  • 2、@Transactional 註解只能應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯, 但是這個被註解的方法將不會展示已配置的事務設定。

  • 3、注意僅僅 @Transactional 註解的出現不足於開啟事務行為,它僅僅 是一種元資料。必須在配置檔案中使用配置元素,才真正開啟了事務行為。

  • 4、Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 註解,而不要使用在類所要實現的任何介面上。在介面上使用 @Transactional 註解,只能當你設定了基於介面的代理時它才生效。因為註解是 不能繼承 的,這就意味著如果正在使用基於類的代理時,那麼事務的設定將不能被基於類的代理所識別,而且物件也將不會被事務代理所包裝。

  以上內容摘自網路,實際應用過程中,儘量只在具體業務類上新增 @Transactional 註解,做到程式設計師只關注具體業務即可。

五、可能異常

  1. org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2: txManager,transactionManager

  2. at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:313)

  3. at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:337)

  4. at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:252)

  5. at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)

  6. at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)

  7. at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)

  8. at com.autonavi.service.TestService$$EnhancerBySpringCGLIB$$272f01cd.buy(<generated>)

  9. at com.autonavi.task.test.ScheduledTest.executeDeliveryTask(ScheduledTest.java:64)

  10. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

  11. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

  12. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

  13. at java.lang.reflect.Method.invoke(Method.java:497)

  14. at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)

  15. at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)

  16. at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)

  17. at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)

  18. at java.util.concurrent.FutureTask.run(FutureTask.java:266)

  19. at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)

  20. at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)

  21. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)

  22. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)

  23. at java.lang.Thread.run(Thread.java:745)

解決方法:

需要指定具體的事物管理器,即將@Transactional改為@Transactional(“txManager”),txManager為定義好的事務管理器。

六、參考資料