引言: 在Spring中@Transactional提供一種控制事務管理的快捷手段,但是很多人都只是@Transactional簡單使用,並未深入瞭解,其各個配置項的使用方法,本文將深入講解各個配置項的使用。
@Transactional的定義
Spring中的@Transactional基於動態代理的機制,提供了一種透明的事務管理機制,方便快捷解決在開發中碰到的問題。在現實中,實際的問題往往比我們預期的要複雜很多,這就要求對@Transactional有深入的瞭解,以來應對複雜問題。
首先我們來看看@Transactional的原始碼定義:
org.springframework.transaction.annotation.Transactional
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
/**
* A qualifier value for the specified transaction.
* <p>May be used to determine the target transaction manager,
* matching the qualifier value (or the bean name) of a specific
* {@link org.springframework.transaction.PlatformTransactionManager}
* bean definition.
*/
String value() default "";
/**
* The transaction propagation type.
* Defaults to {@link Propagation#REQUIRED}.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior()
*/
Propagation propagation() default Propagation.REQUIRED;
/**
* The transaction isolation level.
* Defaults to {@link Isolation#DEFAULT}.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getIsolationLevel()
*/
Isolation isolation() default Isolation.DEFAULT;
/**
* The timeout for this transaction.
* Defaults to the default timeout of the underlying transaction system.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getTimeout()
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
/**
* {@code true} if the transaction is read-only.
* Defaults to {@code false}.
* <p>This just serves as a hint for the actual transaction subsystem;
* it will <i>not necessarily</i> cause failure of write access attempts.
* A transaction manager which cannot interpret the read-only hint will
* <i>not</i> throw an exception when asked for a read-only transaction.
* @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
*/
boolean readOnly() default false;
/**
* Defines zero (0) or more exception {@link Class classes}, which must be a
* subclass of {@link Throwable}, indicating which exception types must cause
* a transaction rollback.
* <p>This is the preferred way to construct a rollback rule, matching the
* exception class and subclasses.
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* Defines zero (0) or more exception names (for exceptions which must be a
* subclass of {@link Throwable}), indicating which exception types must cause
* a transaction rollback.
* <p>This can be a substring, with no wildcard support at present.
* A value of "ServletException" would match
* {@link javax.servlet.ServletException} and subclasses, for example.
* <p><b>NB: </b>Consider carefully how specific the pattern is, and whether
* to include package information (which isn't mandatory). For example,
* "Exception" will match nearly anything, and will probably hide other rules.
* "java.lang.Exception" would be correct if "Exception" was meant to define
* a rule for all checked exceptions. With more unusual {@link Exception}
* names such as "BaseBusinessException" there is no need to use a FQN.
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String exceptionName)}
*/
String[] rollbackForClassName() default {};
/**
* Defines zero (0) or more exception {@link Class Classes}, which must be a
* subclass of {@link Throwable}, indicating which exception types must <b>not</b>
* cause a transaction rollback.
* <p>This is the preferred way to construct a rollback rule, matching the
* exception class and subclasses.
* <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class clazz)}
*/
Class<? extends Throwable>[] noRollbackFor() default {};
/**
* Defines zero (0) or more exception names (for exceptions which must be a
* subclass of {@link Throwable}) indicating which exception types must <b>not</b>
* cause a transaction rollback.
* <p>See the description of {@link #rollbackForClassName()} for more info on how
* the specified names are treated.
* <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String exceptionName)}
*/
String[] noRollbackForClassName() default {};
}
複製程式碼
基於原始碼,我們可以發現在@Transactional中,原來有這麼多的屬性可以進行配置,從而達到複雜應用控制的目的。具體各個屬性的用法和作用,將在下面逐一進行講解和說明。
@Transactional註解中常用引數說明
引數名稱 | 功能描述
-------------|---------------
readOnly |該屬性用於設定當前事務是否為只讀事務,設定為true表示只讀,false則表示可讀寫,預設值為false。例如:@Transactional(readOnly=true)
rollbackFor|該屬性用於設定需要進行回滾的異常類陣列,當方法中丟擲指定異常陣列中的異常時,則進行事務回滾。例如:指定單一異常類:@Transactional(rollbackFor=RuntimeException.class)指定多個異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
rollbackForClassName|該屬性用於設定需要進行回滾的異常類名稱陣列,當方法中丟擲指定異常名稱陣列中的異常時,則進行事務回滾。例如:指定單一異常類名稱:@Transactional(rollbackForClassName="RuntimeException")指定多個異常類名稱:@Transactional(rollbackForClassName={"RuntimeException","Exception"})
noRollbackFor|該屬性用於設定不需要進行回滾的異常類陣列,當方法中丟擲指定異常陣列中的異常時,不進行事務回滾。例如:指定單一異常類:@Transactional(noRollbackFor=RuntimeException.class)指定多個異常類:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})
noRollbackForClassName|該屬性用於設定不需要進行回滾的異常類名稱陣列,當方法中丟擲指定異常名稱陣列中的異常時,不進行事務回滾。例如:指定單一異常類名稱:@Transactional(noRollbackForClassName="RuntimeException")指定多個異常類名稱:@Transactional(noRollbackForClassName={"RuntimeException","Exception"})
propagation|該屬性用於設定事務的傳播行為,具體取值可參考表6-7。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
isolation|該屬性用於設定底層資料庫的事務隔離級別,事務隔離級別用於處理多事務併發的情況,通常使用資料庫的預設隔離級別即可,基本不需要進行設定
timeout|該屬性用於設定事務的超時秒數,預設值為-1表示永不超時
事物傳播行為介紹:
@Transactional(propagation=Propagation.REQUIRED) :如果有事務, 那麼加入事務, 沒有的話新建一個(預設情況下)
@Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不為這個方法開啟事務
@Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事務,都建立一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務
@Transactional(propagation=Propagation.MANDATORY) :必須在一個已有的事務中執行,否則丟擲異常
@Transactional(propagation=Propagation.NEVER) :必須在一個沒有的事務中執行,否則丟擲異常(與Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) :如果其他bean呼叫這個方法,在其他bean中宣告事務,那就用事務.如果其他bean沒有宣告事務,那就不用事務.
@Transactional(propagation=Propagation.NESTED) : 如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則進行與PROPAGATION_REQUIRED類似的操作。
前六個策略類似於EJB CMT,第七個(PROPAGATION_NESTED)是Spring所提供的一個特殊變數。 它要求事務管理器或者使用JDBC 3.0 Savepoint API提供巢狀事務行為(如Spring的DataSourceTransactionManager)
複製程式碼
事物超時設定:
@Transactional(timeout=30) //預設是30秒
事務隔離級別:
@Transactional(isolation = Isolation.READ_UNCOMMITTED):讀取未提交資料(會出現髒讀, 不可重複讀) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED):讀取已提交資料(會出現不可重複讀和幻讀)
@Transactional(isolation = Isolation.REPEATABLE_READ):可重複讀(會出現幻讀)
@Transactional(isolation = Isolation.SERIALIZABLE):序列化
MYSQL: 預設為REPEATABLE_READ級別
SQLSERVER: 預設為READ_COMMITTED
髒讀 : 一個事務讀取到另一事務未提交的更新資料
不可重複讀 : 在同一事務中, 多次讀取同一資料返回的結果有所不同, 換句話說,
後續讀取可以讀到另一事務已提交的更新資料.
可重複讀:在同一事務中多次
讀取資料時, 能夠保證所讀資料一樣, 也就是後續讀取不能讀到另一事務已提交的更新資料
複製程式碼
幻讀 : 一個事務讀到另一個事務已提交的insert資料
注意的幾點:
1、@Transactional 只能被應用到public方法上, 對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能.
2、用 spring 事務管理器,由spring來負責資料庫的開啟,提交,回滾.預設遇到執行期例外(throw new RuntimeException("註釋");)會回滾,即遇到不受檢查(unchecked)的例外時回滾;而遇到需要捕獲的例外(throw new Exception("註釋");)不會回滾,即遇到受檢查的例外(就是非執行時丟擲的異常,編譯器會檢查到的異常叫受檢查例外或說受檢查異常)時,需我們指定方式來讓事務回滾要想所有異常都回滾,要加上 @Transactional( rollbackFor={Exception.class,其它異常}) .如果讓unchecked例外不回滾: @Transactional(notRollbackFor=RunTimeException.class)
如下:
1 @Transactional(rollbackFor=Exception.class) //指定回滾,遇到異常Exception時回滾
2 public void methodName() {
3    throw new Exception("註釋");
4 }
5 @Transactional(noRollbackFor=Exception.class)//指定不回滾,遇到執行期例外(throw new RuntimeException("註釋");)會回滾
6 public ItimDaoImpl getItemDaoImpl() {
7    throw new RuntimeException("註釋");
8 }
複製程式碼
3、@Transactional 註解應該只被應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯, 但是這個被註解的方法將不會展示已配置的事務設定。
4、@Transactional 註解可以被應用於介面定義和介面方法、類定義和類的 public 方法上。然而,請注意僅僅 @Transactional 註解的出現不足於開啟事務行為,它僅僅 是一種後設資料,能夠被可以識別 @Transactional 註解和上述的配置適當的具有事務行為的beans所使用。上面的例子中,其實正是 元素的出現 開啟 了事務行為。
5、Spring團隊的建議是你在具體的類(或類的方法)上使用 @Transactional 註解,而不要使用在類所要實現的任何介面上。你當然可以在介面上使用 @Transactional 註解,但是這將只能當你設定了基於介面的代理時它才生效。因為註解是不能繼承的,這就意味著如果你正在使用基於類的代理時,那麼事務的設定將不能被基於類的代理所識別,而且物件也將不會被事務代理所包裝(將被確認為嚴重的)。因此,請接受Spring團隊的建議並且在具體的類上使用 @Transactional 註解。
補充
例子講解以上七中事務傳播機制
自己寫的一個測試
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class UserBaseInfoControllerTest {
@Autowired
private UserBaseInfoService userBaseInfoService;
@Test
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = RuntimeException.class)
public void testOne() {
UserBaseInfo userBaseInfo = new UserBaseInfo();
userBaseInfo.setId(3);
userBaseInfo.setUserName("xu2");
userBaseInfo.setPassWord("111111");
userBaseInfoService.update(userBaseInfo);
userBaseInfo.setUserName("xu1");
userBaseInfo.setPassWord("1111111");
userBaseInfoService.save(userBaseInfo);
}
}
@Service
public class UserBaseInfoServiceImpl extends AbstractService<UserBaseInfo> implements UserBaseInfoService {
@Resource
private UserBaseInfoMapper userBaseInfoMapper;
@Override
public void save(UserBaseInfo userBaseInfo) {
mapper.insertSelective(userBaseInfo);
}
@Override
//@Transactional(propagation = Propagation.REQUIRED,rollbackFor = RuntimeException.class)
//@Transactional(propagation=Propagation.REQUIRES_NEW,rollbackFor = RuntimeException.class)
//@Transactional(propagation=Propagation.MANDATORY,rollbackFor = RuntimeException.class)
//@Transactional(propagation=Propagation.NESTED,rollbackFor = RuntimeException.class)
//@Transactional(propagation=Propagation.NEVER,rollbackFor = RuntimeException.class)
//@Transactional(propagation=Propagation.NOT_SUPPORTED,rollbackFor = RuntimeException.class)
//@Transactional(propagation=Propagation.SUPPORTS,rollbackFor = RuntimeException.class)
public void update(UserBaseInfo userBaseInfo) {
mapper.updateByPrimaryKeySelective(userBaseInfo);
}
複製程式碼
假設有類A的方法methodB(),有類B的方法methodB().
- PROPAGATION_REQUIRED
如果B的方法methodB()的事務傳播特性是propagation_required,那麼如下圖
A.methodA()呼叫B的methodB()方法,那麼如果A的方法包含事務,則B的方法則不從新開啟事務,
1、 如果B的methodB()丟擲異常,A的methodB()沒有捕獲,則A和B的事務都會回滾;
2、 如果B的methodB()執行期間異常會導致B的methodB()的回滾,A如果捕獲了異常,並正常提交事務,則會發生Transaction rolled back because it has been marked as rollback-only的異常。
3、 如果A的methodA()執行期間異常,則A和B的Method的事務都會被回滾
- PROPAGATION_SUPPORTS
如果B的方法methodB()的事務傳播特性是propagation_supports,麼如下圖
A.methodA()呼叫B的methodB()方法,那麼如果A的方法包含事務,則B執行在此事務環境中,如果A的方法不包含事務,則B執行在非事務環境;
1、如果A沒有事務,則A和B的執行出現異常都不會回滾。
2、如果A有事務,A的method方法執行丟擲異常,B.methodB和A.methodA都會回滾。
3、如果A有事務,B.method丟擲異常,B.methodB和A.methodA都會回滾,如果A捕獲了B.method丟擲的異常,則會出現異常Transactionrolled back because it has been marked as rollback-only。
- PROPAGATION_MANDATORY
表示當前方法必須在一個事務中執行,如果沒有事務,將丟擲異常,如下圖呼叫關係:
B.methodB()事務傳播特性定義為:PROPAGATION_MANDATORY
1、如果A的methoda()方法沒有事務執行環境,則B的methodB()執行的時候會報如下異常:No existingtransaction found for transaction marked with propagation 'mandatory'
2、如果A的Methoda()方法有事務並且執行過程中丟擲異常,則A.methoda()和B.methodb()執行的操作被回滾;
3、如果A的methoda()方法有事務,則B.methodB()丟擲異常時,A的methoda()和B.methodB()都會被回滾;如果A捕獲了B.method丟擲的異常,則會出現異常Transaction rolled back because ithas been marked as rollback-only
- PROPAGATION_NESTED
如有一下方法呼叫關係,如圖:
B的methodB()定義的事務為PROPAGATION_NESTED;
1、 如果A的MethodA()不存在事務,則B的methodB()執行在一個新的事務中,B.method()丟擲的異常,B.methodB()回滾,但A.methodA()不回滾;如果A.methoda()丟擲異常,則A.methodA()和B.methodB()操作不回。
2、 如果A的methodA()存在事務,則A的methoda()丟擲異常,則A的methoda()和B的Methodb()都會被回滾;
3、 如果A的MethodA()存在事務,則B的methodB()丟擲異常,B.methodB()回滾,如果A不捕獲異常,則A.methodA()和B.methodB()都會回滾,如果A捕獲異常,則B.methodB()回滾,A不回滾;
5)PROPAGATION_NEVER
表示事務傳播特性定義為PROPAGATION_NEVER的方法不應該執行在一個事務環境中
有如下呼叫關係:
如果B.methodB()的事務傳播特性被定義為PROPAGATION_NEVER,則如果A.methodA()方法存在事務,則會出現異常Existingtransaction found for transaction marked with propagation 'never'。
6)PROPAGATION_REQUIRES_NEW
表示事務傳播特性定義為PROPAGATION_REQUIRES_NEW的方法需要執行在一個新的事務中。
如有以下呼叫關係:B.methodB()事務傳播特性為PROPAGATION_REQUIRES_NEW.
1、 如果A存在事務,A.methodA()丟擲異常,A.methodA()的事務被回滾,但B.methodB()事務不受影響;如果B.methodB()丟擲異常,A不捕獲的話,A.methodA()和B.methodB()的事務都會被回滾。如果A捕獲的話,A.methodA()的事務不受影響但B.methodB()的事務回滾。
- PROPAGATION_NOT_SUPPORTED
表示該方法不應該在一個事務中執行。如果有一個事務正在執行,他將在執行期被掛起,直到這個事務提交或者回滾才恢復執行。
如有一下呼叫關係圖:
如果B.methodB()方法傳播特性被定義為:PROPAGATION_NOT_SUPPORTED。
1、 如果A.methodA()存在事務,如果B.methodB()丟擲異常,A.methodA()不捕獲的話,A.methodA()的事務被回滾,而B.methodB()出現異常前資料庫操作不受影響。如果A.methodA()捕獲的話,則A.methodA()的事務不受影響,B.methodB()異常丟擲前的資料操作不受影響。