引言java
@Transactional
註解相信你們並不陌生,平時開發中很經常使用的一個註解,它能保證方法內多個數據庫操做要麼同時成功、要麼同時失敗。使用@Transactional
註解時須要注意許多的細節,否則你會發現@Transactional
老是莫名其妙的就失效了。面試
下面咱們從what ,where,when,四個方面完全弄明白如何回答面試官的問題。spring
事務(Transaction),通常是指要作的或所作的事情。在計算機術語中是指訪問並可能更新數據庫中各類數據項的一個程序執行單元(unit)。數據庫
這裏咱們以取錢的例子來說解:好比你去ATM機取1000塊錢,大致有兩個步驟:第一步輸入密碼金額,銀行卡扣掉1000元錢;第二步從ATM出1000元錢。這兩個步驟必須是要麼都執行要麼都不執行。若是銀行卡扣除了1000塊可是ATM出錢失敗的話,你將會損失1000元;若是銀行卡扣錢失敗可是ATM卻出了1000塊,那麼銀行將損失1000元。編程
如何保證這兩個步驟不會出現一個出現異常了,而另外一個執行成功呢?事務就是用來解決這樣的問題。事務是一系列的動做,它們綜合在一塊兒纔是一個完整的工做單元,這些動做必須所有完成,若是有一個失敗的話,那麼事務就會回滾到最開始的狀態,彷彿什麼都沒發生過同樣。在企業級應用程序開發中,事務管理是必不可少的技術,用來確保數據的完整性和一致性。app
在咱們平常開發中事務分爲聲明式事務和編程式事務。性能
編程式事務ui
是指在代碼中手動的管理事務的提交、回滾等操做,代碼侵入性比較強。this
編程式事務指的是經過編碼方式實現事務,容許用戶在代碼中精肯定義事務的邊界。編碼
即相似於JDBC編程實現事務管理。管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。
對於編程式事務管理,spring推薦使用TransactionTemplate。
try { //TODO something transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); throw new InvoiceApplyException("異常"); }複製代碼
聲明式事務
管理創建在AOP之上的。其本質是對方法先後進行攔截,而後在目標方法開始以前建立或者加入一個事務,在執行完目標方法以後根據執行狀況提交或者回滾事務。
聲明式事務最大的優勢就是不須要經過編程的方式管理事務,這樣就不須要在業務邏輯代碼中摻瑣事務管理的代碼,只需在配置文件中作相關的事務規則聲明(或經過基於@Transactional註解的方式),即可以將事務規則應用到業務邏輯中。
簡單地說,編程式事務侵入到了業務代碼裏面,可是提供了更加詳細的事務管理;
而聲明式事務因爲基於AOP,因此既能起到事務管理的做用,又能夠不影響業務代碼的具體實現。
聲明式事務也有兩種實現方式,一是基於TX
和AOP
的xml配置文件方式,二種就是基於@Transactional註解了。
@GetMapping("/user") @Transactional public String user() { int insert = userMapper.insert(userInfo); } 複製代碼
@Transactional 能夠做用在接口
、類
、類方法
。
public
方法都配置相同的事務屬性信息。@Transactional @RestController @RequestMapping public class MybatisPlusController { @Autowired private UserMapper userMapper; @Transactional(rollbackFor = Exception.class) @GetMapping("/user") public String test() throws Exception { User user = new User(); user.setName("javaHuang"); user.setAge("2"); user.setSex("2"); int insert = userMapper.insert(cityInfoDict); return insert + ""; } } 複製代碼複製代碼
propagation
表明事務的傳播行爲,默認值爲 Propagation.REQUIRED
,其餘的屬性信息以下:
Propagation.REQUIRED
:若是當前存在事務,則加入該事務,若是當前不存在事務,則建立一個新的事務。( 也就是說若是A方法和B方法都添加了註解,在默認傳播模式下,A方法內部調用B方法,會把兩個方法的事務合併爲一個事務 ) Propagation.SUPPORTS
:若是當前存在事務,則加入該事務;若是當前不存在事務,則以非事務的方式繼續運行。Propagation.MANDATORY
:若是當前存在事務,則加入該事務;若是當前不存在事務,則拋出異常。Propagation.REQUIRES_NEW
:從新建立一個新的事務,若是當前存在事務,暫停當前的事務。( 當類A中的 a 方法用默認Propagation.REQUIRED
模式,類B中的 b方法加上採用 Propagation.REQUIRES_NEW
模式,而後在 a 方法中調用 b方法操做數據庫,然而 a方法拋出異常後,b方法並無進行回滾,由於Propagation.REQUIRES_NEW
會暫停 a方法的事務 ) Propagation.NOT_SUPPORTED
:以非事務的方式運行,若是當前存在事務,暫停當前的事務。Propagation.NEVER
:以非事務的方式運行,若是當前存在事務,則拋出異常。Propagation.NESTED
:和 Propagation.REQUIRED 效果同樣。isolation
:事務的隔離級別,默認值爲 Isolation.DEFAULT
。
TransactionDefinition.ISOLATION_DEFAULT
這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,一般這值就是
TransactionDefinition.ISOLATION_READ_UNCOMMITTED
該隔離級別表示一個事務能夠讀取另外一個事務修改但尚未提交的數據。該級別不能防止髒讀,不可重複讀和幻讀,所以不多使用該隔離級別。好比PostgreSQL實際上並無此級別。
TransactionDefinition.ISOLATION_READ_COMMITTED
該隔離級別表示一個事務只能讀取另外一個事務已經提交的數據。該級別能夠防止髒讀,這也是大多數狀況下的推薦值。TransactionDefinition.ISOLATION_REPEATABLE_READ
該隔離級別表示一個事務在整個過程當中能夠屢次重複執行某個查詢,而且每次返回的記錄都相同。該級別能夠防止髒讀和不可重複讀。
TransactionDefinition.ISOLATION_SERIALIZABLE
全部的事務依次逐個執行,這樣事務之間就徹底不可能產生干擾,也就是說,該級別能夠防止髒讀、不可重複讀以及幻讀。可是這將嚴重影響程序的性能。一般狀況下也不會用到該級別。
timeout
:事務的超時時間,默認值爲 -1。若是超過該時間限制但事務尚未完成,則自動回滾事務。
readOnly
:指定事務是否爲只讀事務,默認值爲 false;爲了忽略那些不須要事務的方法,好比讀取數據,能夠設置 read-only 爲 true。
rollbackFor
:用於指定可以觸發事務回滾的異常類型,能夠指定多個異常類型。
noRollbackFor
:拋出指定的異常類型,不回滾事務,也能夠指定多個異常類型。
面試官就直接問我有沒有用過@Transactional,我確定不能說沒用過啊,十分自信的說,經常使用。
面試官又問我,在實際開發過程有沒有遇到過@Transactional失效的狀況,我確定不能說沒有啊,再次十分自信的說到,常常。
面試官一臉問號,常常???那你給我說說@Transactional在何時會失效呢?
下面的內容是我將我面試時說的失效場景整理了一下。
若是Transactional
註解應用在非public
修飾的方法上,Transactional將會失效。
之因此會失效是由於在Spring AOP 代理時,TransactionInterceptor(事務攔截器)在目標方法執行先後進行攔截,DynamicAdvisedInterceptor(CglibAopProxy 的內部類)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法會間接調用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute`方法,獲取Transactional 註解的事務配置信息。
protected TransactionAttribute computeTransactionAttribute(Methodmethod, Class<?> targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() &&!Modifier.isPublic(method.getModifiers())) { return null; }複製代碼
Modifier.isPublic會檢查目標方法的修飾符是否爲 public,不是 public則不會獲取@Transactional 的屬性配置信息。
注意:protected
、private
修飾的方法上使用 @Transactional
註解,雖然事務無效,但不會有任何報錯,這是咱們很容犯錯的一點。
數據庫引擎要支持事務,若是是MySQL,注意表要使用支持事務的引擎,好比innodb,若是是myisam,事務是不起做用的。
在上面解讀propagation 屬性的時候,咱們知道
TransactionDefinition.PROPAGATION_SUPPORTS
若是當前存在事務,則加入該事務;若是當前沒有事務,則以非事務的方式繼續運行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
以非事務方式運行,若是當前存在事務,則把當前事務掛起。
TransactionDefinition.PROPAGATION_NEVER
以非事務方式運行,若是當前存在事務,則拋出異常。
當咱們將propagation 屬性設置爲上述三種時,@Transactional 註解就不會產生效果
上述咱們解讀rollbackFor 屬性的時候咱們知道
rollbackFor
能夠指定可以觸發事務回滾的異常類型。
Spring默認拋出了未檢查unchecked
異常(繼承自 RuntimeException
的異常)或者 Error
纔回滾事務;
其餘異常不會觸發回滾事務。若是在事務中拋出其餘類型的異常,但卻指望 Spring 可以回滾事務,就須要指定 rollbackFor屬性。
// 但願自定義的異常能夠進行回滾 @Transactional(propagation= Propagation.REQUIRED,rollbackFor=MyException.class複製代碼
若在目標方法中拋出的異常是 rollbackFor
指定的異常的子類,事務一樣會回滾。Spring源碼以下:
private int getDepth(Class<?> exceptionClass, int depth) { if (exceptionClass.getName().contains(this.exceptionName)) { // Found it! return depth; } // If we've gone as far as we can go and haven't found it... if (exceptionClass == Throwable.class) { return -1; } return getDepth(exceptionClass.getSuperclass(), depth + 1); }複製代碼
咱們來看下面的場景:
好比有一個類User,它的一個方法A,A再調用本類的方法B(不論方法B是用public仍是private修飾),但方法A沒有聲明註解事務,而B方法有。則外部調用方法A以後,方法B的事務是不會起做用的。這也是常常犯錯誤的一個地方。
那爲啥會出現這種狀況?其實這仍是因爲使用Spring AOP
代理形成的,由於只有當事務方法被當前類之外的代碼調用時,纔會由Spring
生成的代理對象來管理。
//@Transactional @GetMapping("/user") private Integer A() throws Exception { User user = new User(); user.setName("javaHuang"); /** * B 插入字段爲 topJavaer的數據 */ this.insertB(); /** * A 插入字段爲 2的數據 */ int insert = userMapper.insert(user); return insert; } @Transactional() public Integer insertB() throws Exception { User user = new User(); user.setName("topJavaer"); return userMapper.insert(user); }複製代碼
這種狀況是最多見的一種@Transactional註解失效場景,
@Transactional private Integer A() throws Exception { int insert = 0; try { User user = new User(); user.setCityName("javaHuang"); user.setUserId(1); /** * A 插入字段爲 javaHuang的數據 */ insert = userMapper.insert(user); /** * B 插入字段爲 topJavaer的數據 */ b.insertB(); } catch (Exception e) { e.printStackTrace(); } }複製代碼
若是B方法內部拋了異常,而A方法此時try catch了B方法的異常,那這個事務就不能正常回滾,而是會報出異常
org.springframework.transaction.UnexpectedRollbackException: Transactionrolled back because it has been marked as rollback-only複製代碼
解決方法:
第一聲明事務的時候加上rollback='exception'
第二 cath代碼塊裏面手動回滾
@Transactional 註解咱們常用,可是每每咱們也只是知道它是一個事務註解,不少時候遇到事務註解失效的狀況下,咱們都是一頭霧水,看不出個因此然來,花費了很長的時間都不能解決。
經過本文了解了@Transactional 註解的失效場景,在之後遇到這種狀況時,基本就能一眼看破,而後摸摸本身光滑的腦門,soga,so easy!
媽媽不再用擔憂我找不到本身寫的bug了。