|
本文討論下Spring注解@Transactional 及其隔離(isolation)和傳播(propagation)屬性的設置. 1. @Transactional注解@Transactional注解可以用在數(shù)據庫事務操作的方法上。并可以設置事務的相關屬性:隔離屬性(isolation), 超時屬性(timeout), 只讀屬性(read-only)以及回滾條件,也可以指定事務管理器。 1.1. 實現(xiàn)細節(jié)Spring創(chuàng)建代理或操縱類字節(jié)碼來管理事務的創(chuàng)建、提交和回滾。使用代理方式Spring會忽略內部方法調用事務,即使有@Transactional注解也會被忽略,代理需要通過其他類進行調用。 例如,一個方法callMethod并標記了 @Transactional注解,Spring會包裝一些事務管理代碼環(huán)繞在方法執(zhí)行過程: createTransactionIfNecessary();
try {
callMethod();
commitTransactionAfterReturning();
} catch (exception) {
completeTransactionAfterThrowing();
throw exception;
}
1.2. 使用@Transactional注解事務注解可以在接口、類或直接在方法上。實際會按照優(yōu)先級進行覆蓋,從低到高優(yōu)先級為:接口、父類、類、接口方法、父類方法、類方法。 對于類級別注解,則Spring應用注解設置至所有沒有標記注解的public方法,如果在private、protecte方法上使用注解,Spring會忽略。 下面通過示例說明: @Transactional
public interface TransferService {
void transfer(String user1, String user2, double val);
}
通常不建議在接口上設置事務,但在Spring Data @Repository情況下是可行的。這里在類上增加注解覆蓋接口或父類的設置: @Service
@Transactional
public class TransferServiceImpl implements TransferService {
@Override
public void transfer(String user1, String user2, double val) {
// ...
}
}
如果在方法增加注解則覆蓋類定義: @Transactional
public void transfer(String user1, String user2, double val) {
// ...
}
2. 事務傳播傳播性定義業(yè)務邏輯的事務邊界。Spring根據傳播性設置負責啟動或暫停事務。 Spring根據傳播屬性調用TransactionManager::getTransaction 方法獲取或創(chuàng)建事務。它支持所有類型的TransactionManager的部分傳播屬性,一些傳播屬性僅被TransactionManager特定實現(xiàn)支持。下面詳細描述不同傳播屬性。 2.1. REQUIREDREQUIRED是缺省屬性。Spring檢查是否有活動事務,如果沒有則創(chuàng)建新的事務,否則業(yè)務邏輯追加至當前活動事務中: @Transactional(propagation = Propagation.REQUIRED)
public void requiredExample(String user) {
// ...
}
缺省屬性也可以不指定: @Transactional
public void requiredExample(String user) {
// ...
}
對應偽代碼如下: if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
return createNewTransaction();
2.2. SUPPORTSSUPPORTS屬性,Spring首先檢查是否有活動事務存在,存在則使用,反之,則無事務進行執(zhí)行。 @Transactional(propagation = Propagation.SUPPORTS)
public void supportsExample(String user) {
// ...
}
對應偽代碼: if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
return emptyTransaction;
2.3. MANDATORY當屬性設置為MANDATORY屬性時,如果存在活動事務,則使用之。反之拋異常,即強制使用事務。 @Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExample(String user) {
// ...
}
對應偽代碼: if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
throw IllegalTransactionStateException;
2.4. NEVERNEVER屬性的邏輯:如果存在活動事務則拋異常。 @Transactional(propagation = Propagation.NEVER)
public void neverExample(String user) {
// ...
}
對應偽代碼: if (isExistingTransaction()) {
throw IllegalTransactionStateException;
}
return emptyTransaction;
2.5. NOT_SUPPORTED如果存在活動事務則Spring首先掛起當前事務,然后業(yè)務邏輯在無事務下執(zhí)行。 @Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExample(String user) {
// ...
}
JTATransactionManager支持開箱即用的事務掛起。其他方法通過持有對事務引用,然后從線程上下文中清除它來模擬掛起。 2.6. REQUIRES_NEWREQUIRES_NEW屬性,如果存在活動事務則Spring掛起當前事務,然后創(chuàng)建新的事務。 @Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExample(String user) {
// ...
}
與NOT_SUPPORTED類似,我們需要JTATransactionManager來執(zhí)行實際的事務掛起。 偽代碼如下: if (isExistingTransaction()) {
suspend(existing);
try {
return createNewTransaction();
} catch (exception) {
resumeAfterBeginException();
throw exception;
}
}
return createNewTransaction();
2.7. NESTED對于NESTED屬性,Spring檢查是否存在事務,如果存在則標記保存點。意味著如果后續(xù)業(yè)務執(zhí)行遇到異常,那么回滾至保存點。如果沒有活動事務時與REQUIRED屬性一樣。 DataSourceTransactionManager 支持該屬性,一些JTATransactionManager的實現(xiàn)可能也支持。JpaTransactionManager僅對JDBC連接支持NESTED屬性,如果設置nestedTransactionAllowed 屬性為true且JDBC驅動支持保存點,那么JPA事務中的JDBC代碼也工作。屬性設置如下: @Transactional(propagation = Propagation.NESTED)
public void nestedExample(String user) {
// ...
}
3. 事務隔離隔離屬性是ACID ( Atomicity, Consistency, Isolation及 Durability)其中之一,隔離描述并發(fā)事務應用的更改如何對彼此可見。每種隔離級別防止事務中零個或多個并發(fā)副作用: 臟讀(Dirty read): 讀取并發(fā)事務中未提交的信息 不可重復讀(Nonrepeatable read): 如果并發(fā)事務更新相同行并提交,重復讀一行獲得不同值 幻讀(Phantom read): 如果其他事務增加或刪除查詢行并提交,則重復查詢一定范圍記錄返回值不同
我們可以通過@Transactional::isolation設置事務的隔離級別。Spring提供了5個枚舉值:DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE。 3.1. 缺省隔離屬性當Spring創(chuàng)建新事務時,缺省隔離級別使用RDBMS,因此改變數(shù)據庫時應該注意。 我們也應該考慮使用不同隔離屬性調用一組方法的場景,正常流程隔離僅應用于新事務創(chuàng)建時。因此出于某種原因,不想讓一個方法在不同的隔離狀態(tài)下執(zhí)行,我們必須將TransactionManager::setValidateExistingTransaction設置為true。偽代碼如下: if (isolationLevel != ISOLATION_DEFAULT) {
if (currentTransactionIsolationLevel() != isolationLevel) {
throw IllegalTransactionStateException
}
}
下面看看其他隔離級別。 3.2. READ_UNCOMMITTEDREAD_UNCOMMITTED是最低的隔離級別,最大化允許并發(fā)訪問。 它受到上述三種并發(fā)性副作用的影響。具有此隔離的事務將讀取其他并發(fā)事務未提交數(shù)據,此外不可重復讀取和幻讀都可能發(fā)生。因此我們可以在重新讀取行或重新執(zhí)行范圍查詢時獲得不同的結果??梢栽诜椒ɑ蝾惿显O置隔離級別: @Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void log(String message) {
// ...
}
Postgres 不支持 READ_UNCOMMITTED 隔離屬性,會用 READ_COMMITED 代替。Oracle 也不支持 READ_UNCOMMITTED。 3.3. READ_COMMITTED第二級隔離READ_COMMITTED可以防止臟讀,其他并發(fā)副作用仍可能發(fā)生。并發(fā)事務的未提交改變沒有影響,但已提交的改變再次查詢也會改變。 設置隔離代碼: @Transactional(isolation = Isolation.READ_COMMITTED)
public void log(String message){
// ...
}
READ_COMMITTED Postgres, SQL Server及Oracle的缺省級別。 3.4. REPEATABLE_READ第三個隔離級別是REPEATABLE_READ,防止臟讀和不可重復讀,因此不會受并發(fā)事務未提交改變影響。當重復查詢行不會得到不同結果,但可能獲得新增記錄或刪除部分行。 該級別可以防止丟失更新,當兩個或多個并發(fā)事務讀并更新相同行會發(fā)生丟失更新。REPEATABLE_READ根本不允許同時訪問行,因此丟失更新不會發(fā)生。 設置代碼: @Transactional(isolation = Isolation.REPEATABLE_READ)
public void log(String message){
// ...
}
REPEATABLE_READ Mysql的缺省級別,Oracle 不支持 REPEATABLE_READ。 3.5. SERIALIZABLESERIALIZABLE 是最高隔離級別??梢苑乐股鲜鏊械膯栴},但也導致最低并發(fā)訪問效率,因為并發(fā)事務按照順序執(zhí)行。也就是并發(fā)執(zhí)行一組SERIALIZABLE級別事務與順序執(zhí)行結果一樣。設置代碼: @Transactional(isolation = Isolation.SERIALIZABLE)
public void log(String message){
// ...
}
4. 總結本文我們探討了事務的傳遞與隔離屬性,并詳細解釋了不同屬性的含義及對并發(fā)事務的影響。
|