在上一篇 Spring Boot事務管理(上)的基礎上介紹Spring Boot事務屬性和事務回滾規則 。
4 Spring Boot事務屬性
什么是事務屬性呢?事務屬性可以理解成事務的一些基本配置,描述了事務策略如何應用到方法上。事務屬性包含了5個方面,如圖所示,它們定義於TransactionDefinition接口類

1、 事務隔離級別
隔離級別是指若干個並發事務之間的隔離程度。
Spring Boot的隔離級別被封裝在枚舉類Isolation,枚舉值取自接口TransactionDefinition 定義,該接口中定義了五個表示隔離級別的常量:
| 隔離級別 |
含義 |
| ISOLATION_DEFAULT |
默認值,使用后端數據庫默認的隔離級別 |
| ISOLATION_READ_UNCOMMITTED |
表示一個事務可以讀取另一個事務修改但還沒有提交的數據。可能導致臟讀,不可重復讀和幻讀,因此很少使用該隔離級別。最低的隔離級別。 |
| ISOLATION_READ_COMMITTED |
表示一個事務只能讀取另一個事務已經提交的數據。可以防止臟讀,但是幻讀或不可重復讀仍有可能發生,這也是大多數情況下的推薦值。 |
| ISOLATION_REPEATABLE_READ |
表示一個事務在整個過程中可以多次重復執行某個查詢並且每次返回的記錄都相同。可以防止臟讀和不可重復讀,但幻讀仍有可能發生。MySQL默認隔離級別。 |
| ISOLATION_SERIALIZABLE |
所有的事務依次逐個執行,事務之間完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。但是這將嚴重影響程序的性能,因為它通常是通過完全鎖定事務相關的數據庫表來實現的。 |
2、 事務傳播規則
事務傳播行為(propagation behavior)用來描述由某一個事務傳播行為修飾的方法被嵌套進另一個方法的時候,事務如何傳播。
傳播行為定義了事務范圍,何時觸發事務,是否暫停現有事務,或者在調用方法是如果沒有事務則失敗等等。
Spring Boot的事務傳播行為常量被封裝在枚舉類Propagation,枚舉值取自接口TransactionDefinition,在接口中定義了如下七個表示傳播行為的常量:
| 傳播行為 |
含義 |
| PROPAGATION_REQUIRED |
如果當前存在事務,則加入該事務;否則,新建一個事務。這是默認值 |
| PROPAGATION_REQUIRES_NEW |
新建事務,如果當前存在事務,則掛起當前事務 |
| PROPAGATION_SUPPORTS |
如果當前存在事務,則加入該事務;否則,以非事務的方式繼續運行 |
| PROPAGATION_NOT_SUPPORTED |
以非事務方式運行,如果當前存在事務,則掛起當前事務 |
| PROPAGATION_NEVER |
以非事務方式運行,如果當前存在事務,則拋異常 |
| PROPAGATION_MANDATORY |
如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常 |
| PROPAGATION_NESTED |
如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於PROPAGATION_REQUIRED |
下面貼出TransactionDefinition中關於傳播行為的源碼。
/** * Support a current transaction; create a new one if none exists. * Analogous to the EJB transaction attribute of the same name. * <p>This is typically the default setting of a transaction definition, * and typically defines a transaction synchronization scope. */
int PROPAGATION_REQUIRED = 0; /** * Support a current transaction; execute non-transactionally if none exists. * Analogous to the EJB transaction attribute of the same name. * <p><b>NOTE:</b> For transaction managers with transaction synchronization, * {@code PROPAGATION_SUPPORTS} is slightly different from no transaction * at all, as it defines a transaction scope that synchronization might apply to. * As a consequence, the same resources (a JDBC {@code Connection}, a * Hibernate {@code Session}, etc) will be shared for the entire specified * scope. Note that the exact behavior depends on the actual synchronization * configuration of the transaction manager! * <p>In general, use {@code PROPAGATION_SUPPORTS} with care! In particular, do * not rely on {@code PROPAGATION_REQUIRED} or {@code PROPAGATION_REQUIRES_NEW} * <i>within</i> a {@code PROPAGATION_SUPPORTS} scope (which may lead to * synchronization conflicts at runtime). If such nesting is unavoidable, make sure * to configure your transaction manager appropriately (typically switching to * "synchronization on actual transaction"). * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#SYNCHRONIZATION_ON_ACTUAL_TRANSACTION */
int PROPAGATION_SUPPORTS = 1; /** * Support a current transaction; throw an exception if no current transaction * exists. Analogous to the EJB transaction attribute of the same name. * <p>Note that transaction synchronization within a {@code PROPAGATION_MANDATORY} * scope will always be driven by the surrounding transaction. */
int PROPAGATION_MANDATORY = 2; /** * Create a new transaction, suspending the current transaction if one exists. * Analogous to the EJB transaction attribute of the same name. * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box * on all transaction managers. This in particular applies to * {@link org.springframework.transaction.jta.JtaTransactionManager}, * which requires the {@code javax.transaction.TransactionManager} to be * made available it to it (which is server-specific in standard Java EE). * <p>A {@code PROPAGATION_REQUIRES_NEW} scope always defines its own * transaction synchronizations. Existing synchronizations will be suspended * and resumed appropriately. * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */
int PROPAGATION_REQUIRES_NEW = 3; /** * Do not support a current transaction; rather always execute non-transactionally. * Analogous to the EJB transaction attribute of the same name. * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box * on all transaction managers. This in particular applies to * {@link org.springframework.transaction.jta.JtaTransactionManager}, * which requires the {@code javax.transaction.TransactionManager} to be * made available it to it (which is server-specific in standard Java EE). * <p>Note that transaction synchronization is <i>not</i> available within a * {@code PROPAGATION_NOT_SUPPORTED} scope. Existing synchronizations * will be suspended and resumed appropriately. * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */
int PROPAGATION_NOT_SUPPORTED = 4; /** * Do not support a current transaction; throw an exception if a current transaction * exists. Analogous to the EJB transaction attribute of the same name. * <p>Note that transaction synchronization is <i>not</i> available within a * {@code PROPAGATION_NEVER} scope. */
int PROPAGATION_NEVER = 5; /** * Execute within a nested transaction if a current transaction exists, * behave like {@link #PROPAGATION_REQUIRED} else. There is no analogous * feature in EJB. * <p><b>NOTE:</b> Actual creation of a nested transaction will only work on * specific transaction managers. Out of the box, this only applies to the JDBC * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager} * when working on a JDBC 3.0 driver. Some JTA providers might support * nested transactions as well. * @see org.springframework.jdbc.datasource.DataSourceTransactionManager */
int PROPAGATION_NESTED = 6;
理解PROPAGATION_NESTED的關鍵是savepoint。他與REQUIRES_NEW的區別REQUIRES_NEW另起一個事務,將會與它的父事務相互獨立,而Nested的事務和父事務是相依的,它的提交是要等待和父事務一塊提交的。也就是說,如果父事務最后回滾,它也要回滾的;如果子事務回滾而且異常被捕獲,父事務無感知,則父事務不回滾。
public class TransactionalDemo {
@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRES_NEW)
public void doSth() {
// do something
}
}
3、 事務超時
所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。
默認設置為底層事務系統的超時值,如果底層數據庫事務系統沒有設置超時值,那么就是none,沒有超時限制。
/** * Return the transaction timeout. * <p>Must return a number of seconds, or {@link #TIMEOUT_DEFAULT}. * <p>Only makes sense in combination with {@link #PROPAGATION_REQUIRED} * or {@link #PROPAGATION_REQUIRES_NEW}. * <p>Note that a transaction manager that does not support timeouts will throw * an exception when given any other timeout than {@link #TIMEOUT_DEFAULT}. * @return the transaction timeout */
int getTimeout();
4、 事務只讀屬性
只讀事務用於只讀取但不修改數據的情形,只讀事務用於特定情景下的優化,比如使用Hibernate的時候。默認為讀寫事務。
//@return {@code true} if the transaction is to be optimized as read-only
boolean isReadOnly();
5、 事務回滾規則
指示Spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內拋出異常。Spring事務管理器會捕捉任何未處理的異常,然后依據規則決定是否回滾拋出異常的事務。默認配置下,Spring只有在拋出的異常為運行時unchecked異常時才回滾該事務,也就是拋出的異常為RuntimeException的子類(Errors也會導致事務回滾),而拋出checked異常則不會導致事務回滾。可以明確的配置在拋出哪些異常時回滾事務,包括checked異常。也可以明確定義哪些異常拋出時不回滾事務。還可以編程性的通過setRollbackOnly()方法來指示一個事務必須回滾,在調用完setRollbackOnly()后你所能執行的唯一操作就是回滾。
Spring的事務邊界是在調用業務方法之前開始的,通過調用connection.setAutoCommit(false)激活事務,業務方法執行完畢之后來執行commit or rollback(Spring默認取決於是否拋出runtime異常)以結束事務。如果拋出runtime exception 並在你的業務方法中沒有catch到的話,事務會回滾。一般不需要在業務方法中catch異常,如果非要catch,在做完你想做的工作后(比如關閉文件等)一定要拋出runtime exception;否則,Spring會將你的操作commit,這樣就會產生臟數據,所以你的catch代碼是畫蛇添足。如:
try {
// bisiness logic code
} catch(Exception e) {
// handle the exception
}
由此可以推知,在Spring中如果某個業務方法被一個try catch整個包裹起來,那么這個業務方法也就等於脫離了Spring事務的管理,因為沒有任何異常會從業務方法中拋出!全被捕獲並吞掉,導致Spring異常拋出觸發事務回滾策略失效。不過,如果在catch代碼塊中采用頁面硬編碼的方式使事務顯式的回滾,這樣寫也未嘗不可。
預知后事如何,請聽下回分解——Spring Boot事務管理(下)
