Java spring事務(wù)及事務(wù)不生效的原因詳解
注解 @Transactional 的屬性參數(shù)
屬性 | 類型 | 描述 |
---|---|---|
value | String | 可選,指定事務(wù)管理器 |
propagation | enum: Propagation | 可選,指定事務(wù)傳播行為 |
isolation | enum: Isolation | 可選,指定事務(wù)隔離級別 |
readOnly | boolean | 讀寫或只讀事務(wù),默認讀寫 |
timeout | int (in seconds granularity) | 事務(wù)超時時間設(shè)置 |
rollbackFor | Class 對象數(shù)組,必須繼承自Throwable | 導致事務(wù)回滾的異常類數(shù)組 |
rollbackForClassName | 類名數(shù)組,必須繼承自Throwable | 導致事務(wù)回滾的異常類名字數(shù)組 |
noRollbackFor | Class對象數(shù)組,必須繼承自Throwable | 不會導致事務(wù)回滾的異常類數(shù)組 |
noRollbackForClassName | 類名數(shù)組,必須繼承自Throwable | 不會導致事務(wù)回滾的 |
propagation 事務(wù)的傳播機制
事務(wù)的傳播機制:如果在開始當前事務(wù)之前,一個事務(wù)上下文已經(jīng)存在,此時有若干選項可以指定一個事務(wù)性方法的執(zhí)行行為
枚舉 Propagation
中定義了 7 個傳播機制的值
Propagation.REQUIRED
:如果當前沒有事務(wù),就新建一個事務(wù),如果已經(jīng)存在一個事務(wù)中,加入到這個事務(wù)中。是 spring 默認的傳播機制Propagation.SUPPORTS
:持當前事務(wù),如果當前有事務(wù),就以事務(wù)方式執(zhí)行;如果當前沒有事務(wù),就以非事務(wù)方式執(zhí)行Propagation.MANDATORY
:使用當前的事務(wù),且必須在一個已有的事務(wù)中執(zhí)行,如果當前不存在事務(wù),否則拋出異常Propagation.REQUIRES_NEW
:不管是否存在事務(wù),都創(chuàng)建一個新的事務(wù),原來的掛起,新的執(zhí)行完畢,繼續(xù)執(zhí)行老的事務(wù)Propagation.NOT_SUPPORTED
:以非事務(wù)方式執(zhí)行,如果當前存在事務(wù),就把當前事務(wù)掛起Propagation.NEVER
:以非事務(wù)方式執(zhí)行,且必須在一個沒有的事務(wù)中執(zhí)行,如果當前存在事務(wù),則拋出異常Propagation.NESTED
:如果當前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行;如果當前沒有事務(wù),則執(zhí)行與Propagation.REQUIRED
類似的操作
isolation 事務(wù)的隔離級別
隔離級別:若干個并發(fā)的事務(wù)之間的隔離程度,與我們開發(fā)時候主要相關(guān)的場景包括:臟讀取、重復讀、幻讀
枚舉 Isolation
中定義了 5 個表示隔離級別的值
Isolation.DEFAULT
:使用各個數(shù)據(jù)庫默認的隔離級別,是 spring 默認的隔離級別Isolation.READ_UNCOMMITTED
:讀取未提交數(shù)據(jù)(會出現(xiàn)臟讀, 不可重復讀)Isolation.READ_COMMITTED
:讀取已提交數(shù)據(jù)(會出現(xiàn)不可重復讀和幻讀)Isolation.REPEATABLE_READ
:可重復讀(會出現(xiàn)幻讀)Isolation.SERIALIZABLE
:串行化
常用數(shù)據(jù)庫的默認隔離級別
MYSQL
:默認為 REPEATABLE_READ
SQLSERVER
:默認為 READ_COMMITTED
Oracle
:默認為 READ_COMMITTED
readOnly 事務(wù)的讀寫性
默認情況下是 false(不指定只讀性);設(shè)置為 true 的含義: 該方法下使用的是只讀操作,如果進行其他非讀操作,則會跑出異常
事務(wù)的只讀性概念
從這一點設(shè)置的時間點開始(時間點 a),到這個事務(wù)結(jié)束的過程中,其他事務(wù)所提交的數(shù)據(jù),該事務(wù)將看不見??!即查詢中不會出現(xiàn)別人在時間點 a 之后提交的數(shù)據(jù)
應(yīng)用場景
- 如果你一次執(zhí)行單條查詢語句,則沒有必要啟用事務(wù)的只讀性支持,數(shù)據(jù)庫默認支持 SQL 執(zhí)行期間的讀一致性
- 如果你一次執(zhí)行多條查詢語句,例如統(tǒng)計查詢,報表查詢。在這種場景下,多條查詢 SQL 必須保證整體的讀一致性;否則,在前條 SQL 查詢之后,后條 SQL 查詢之前,數(shù)據(jù)被其他用戶改變,則該次整體的統(tǒng)計查詢將會出現(xiàn)讀數(shù)據(jù)不一致的狀態(tài)。此時,就有必要啟用事務(wù)的只讀性支持
是一次執(zhí)行多次查詢來統(tǒng)計某些信息,這時為了保證數(shù)據(jù)整體的一致性,要用只讀事務(wù)
timeout 超時時間
- 用于設(shè)置事務(wù)處理的時間長度,阻止可能出現(xiàn)的長時間的阻塞系統(tǒng)或者占用系統(tǒng)資源,單位為秒
- 如果超時設(shè)置事務(wù)回滾,并拋出 TransactionTimedOutException 異常
rollbackFor 和 rollbackForClassName 遇到時回滾
- 用來指明回滾的條件是哪些異常類或者異常類名
- spring 默認情況下會對運行期異常 RunTimeException 進行事務(wù)回滾,如果遇到 checked 異常就不回滾
noRollbackFor 和 noRollbackForClassName 遇到時不回滾
用來指明不回滾的條件是哪些異常類或者異常類名
value 指定使用的事務(wù)管理器
- value 主要用來指定不同的事務(wù)管理器,主要用來滿足在同一個系統(tǒng)中,存在不同的事務(wù)管理器的場景需要
- 比如,在 spring 中聲明了兩種事務(wù)管理器 txManager1,txManager2。然后用戶可以根據(jù)需要,修改這個參數(shù)來指定特定的 txManage
存在多個事務(wù)管理器的情況:在一個系統(tǒng)中,需要訪問多個數(shù)據(jù)源,則必然會配置多個事務(wù)管理器
spring 事務(wù)不生效的原因
spring
團隊建議在具體的 類或類的方法上
使用 @Transactional
注解,而不要使用在類所要實現(xiàn)的任何接口上。在接口上使用 @Transactional
注解,只能當你設(shè)置了基于接口的代理時它才生效。因為注解是不能繼承的,這就意味著如果正在使用基于類的代理時,那么事務(wù)的設(shè)置將不能被基于類的代理所識別,而且對象也將不會被事務(wù)代理所包裝
對 spring 來說,方法調(diào)用者所屬的類或方法調(diào)用者就是主體對象,spring 會從二者身上獲取合適的事務(wù)增強器和事務(wù)屬性,如果獲取不到合適的增強器和事務(wù)屬性,那么事務(wù)就會失效
數(shù)據(jù)庫引擎不支持事務(wù)
比如我們常用的 mysql,從 mysql 5.5.5 開始的默認存儲引擎是 InnoDB,之前默認的都是 MyISAM,引擎 MyISAM 是不支持事務(wù)操作的,需要改成 InnoDB 才能支持。所以這點要值得注意,底層引擎不支持事務(wù)再怎么搞都是白搭
@Transactional 所在類非 spring 容器的 bean
// @Service public class OrderServiceImpl implements OrderService { @Transactional public void updateOrder(Order order) { // update order } }
如果此時把 @Service 注解注釋掉,這個類就不會被加載成一個 bean,那這個類就不會被 spring 管理了,事務(wù)自然就失效了
方法不是 public 的
Method visibility and @Transactional When you use proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. If you need to annotate non-public methods, consider using AspectJ (described later).
@Transactional
只能用于 public
的方法上,否則事務(wù)會失效;即使方法是 public
的,但是如果被 private
的方法調(diào)用,事務(wù)同樣也會失效
數(shù)據(jù)源沒有配置事務(wù)管理器
@Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
當前數(shù)據(jù)源如果沒有配置事務(wù)管理器,那事務(wù)是不會生效的
事務(wù)的 propagation 傳播機制設(shè)置錯誤
@Service public class OrderServiceImpl implements OrderService { @Transactional public void update(Order order) { updateOrder(order); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void updateOrder(Order order) { // update order } }
Propagation.NOT_SUPPORTED
:以非事務(wù)方式執(zhí)行,如果當前存在事務(wù),就把當前事務(wù)掛起
catch 語句沒有拋出異常
@Service public class ClassServiceImpl implements ClassService { @Override @Transactional public void insertClassByException(ClassDo classDo) { classMapper.insertClass(classDo); try { int i = 1 / 0; } catch (Exception e) { e.printStackTrace(); } } }
把異常吃了,然后又不拋出來,事務(wù)也不會回滾!
拋出的異常類型錯誤
@Service public class OrderServiceImpl implements OrderService { @Transactional public void updateOrder(Order order) { try { // update order } catch { throw new Exception("更新錯誤"); } } }
@Transactional
默認回滾的是 RuntimeException
和 Error
,而 Exception
是 RuntimeException
的父類,事務(wù)不生效的
如果你想觸發(fā)其他異常的回滾,需要在注解上配置一下,如
@Transactional(rollbackFor = Exception.class)
確保業(yè)務(wù)和事務(wù)入口在同一個線程
@Transactional @Override public void save(User user1, User user2) { new Thread(() -> { saveError(user1, user2); System.out.println(1 / 0); }).start(); }
自身調(diào)用問題
案列一
@Transactional
的事務(wù)開啟,或者是基于接口的或者是基于類的代理被創(chuàng)建。所以在同一個類中一個無事務(wù)的方法調(diào)用另一個有事務(wù)的方法
,事務(wù)是不會起作用的 (這就是業(yè)界老問題:類內(nèi)部方法調(diào)用事務(wù)不生效的問題原因)
因為 addInfo()
上沒有事務(wù),而 addInfo()
調(diào)用 create()
的時候是類內(nèi)部調(diào)用,沒有走代理類,也就沒有事務(wù)切面
案列二
事務(wù)生效
由于 spring 事務(wù)默認的傳播機制是 Propagation.REQUIRED
,create()
方法的事務(wù)會加入到 addInfo()
方法的事務(wù)之中;而所在的類是可以產(chǎn)生代理對象的
案列三
事務(wù)生效
由于 spring 事務(wù)默認的傳播機制是 Propagation.REQUIRED
,create()
方法的事務(wù)會加入到 addInfo()
方法的事務(wù)之中;而所在的類是可以產(chǎn)生代理對象的
案列四
事務(wù)生效
案列五
事務(wù)生效
這里雖然是方法內(nèi)部調(diào)用,但是事務(wù)切入了 addInfo() 方法
,所以即使內(nèi)部拋出異常,也是可以生效的
案列六
事務(wù)不生效
案列七
事務(wù)生效
這是我們解決方法內(nèi)部調(diào)用事務(wù)不生效的最常用方法之一:內(nèi)部維護一個注入自己的 bean,然后使用這個屬性來調(diào)用方法。其實還有一種方法,那就是利用 Aop 上下文來獲取代理對象((TestService)AopContext.currentProxy()).create();,
然后通過代理對象來調(diào)用。這里需要注意:Aop 上下文 spring 默認是關(guān)閉的,需要手動開啟
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之二叉樹
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之二叉樹,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01IDEA與模擬器安裝調(diào)試失敗的處理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES
這篇文章主要介紹了IDEA與模擬器安裝調(diào)試失敗的處理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09基于Idea+Jconsole實現(xiàn)線程監(jiān)控步驟
這篇文章主要介紹了基于Idea+Jconsole實現(xiàn)線程監(jiān)控功能,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-04-04spring.mvc.servlet.load-on-startup屬性方法源碼解讀
這篇文章主要介紹了spring.mvc.servlet.load-on-startup的屬性方法源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12