文章目錄
Spring Boot JPA 中transaction的使用
transaction是我們在做數據庫操作的時候不能回避的一個話題,通過transaction,我們可以保證數據庫操作的原子性,一致性,隔離性和持久性。
本文我們將會深入的探討Spring Boot JPA中@Transactional注解的使用。
通過@Transactional注解,我們可以設置事物的傳播級別和隔離級別,同時可以設置timeout, read-only, 和 rollback等特性。
@Transactional的實現
Spring通過創建代理或者操縱字節碼來實現事物的創建,提交和回滾操作。如果是代理模式的話,Spring會忽略掉@Transactional的內部方法調用。
如果我們有個方法callMethod,並標記它為@Transactional,那么Spring Boot的實現可能是如下方式:
createTransactionIfNecessary();
try {
callMethod();
commitTransactionAfterReturning();
} catch (exception) {
completeTransactionAfterThrowing();
throw exception;
}
@Transactional的使用
@Transactional使用起來很簡單,可以放在class上,可以放在interface上,也可以放在方法上面。
如果放在方法上面,那么該方法中的所有public方法都會應用該Transaction。
如果@Transactional放在private方法上面,則Spring Boot將會忽略它。
Transaction的傳播級別
傳播級別Propagation定義了Transaction的邊界,我們可以很方便的在@Transactional注解中定義不同的傳播級別。
下面我們來分別看一下Transaction的傳播級別。
REQUIRED
REQUIRED是默認的傳播級別,下面的兩種寫法是等價的:
@Transactional
public void deleteBookWithDefaultTransaction(Long id) {
bookRepository.deleteBookById(id);
}
@Transactional(propagation = Propagation.REQUIRED)
public void deleteBookWithRequired(Long id) {
}
Spring會檢測現在是否有一個有效的transaction。如果沒有則創建,如果有transaction,則Spring將會把該放方法的業務邏輯附加到已有的transaction中。
我們再看下REQUIRED的偽代碼:
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
return createNewTransaction();
SUPPORTS
在SUPPORTS的情況下,Spring首先會去檢測是否有存在Transaction,如果存在則使用,否則不會使用transaction。
我們看下代碼怎么使用:
@Transactional(propagation = Propagation.SUPPORTS)
public void deleteBookWithSupports(Long id) {
}
SUPPORTS的實現偽代碼如下:
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
return emptyTransaction;
MANDATORY
在MANDATORY情況下,Spring先會去檢測是否有一個Transaction存在,如果存在則使用,否則拋出異常。
我們看下代碼怎么使用:
@Transactional(propagation = Propagation.MANDATORY)
public void deleteBookWithMandatory(Long id) {
}
MANDATORY的實現邏輯如下:
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
throw IllegalTransactionStateException;
NEVER
如果是NEVER的情況下,如果現在有一個Transaction存在,則Spring會拋出異常。
使用的代碼如下:
@Transactional(propagation = Propagation.NEVER)
public void deleteBookWithNever(Long id) {
}
實現邏輯代碼如下:
if (isExistingTransaction()) {
throw IllegalTransactionStateException;
}
return emptyTransaction;
NOT_SUPPORTED
如果使用的是NOT_SUPPORTED,那么Spring將會首先暫停現有的transaction,然后在非transaction情況下執行業務邏輯。
我們這樣使用:
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void deleteBookWithNotSupported(Long id) {
}
REQUIRES_NEW
當REQUIRES_NEW使用時,Spring暫停當前的Transaction,並創建一個新的。
我們看下代碼怎么使用:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deleteBookWithRequiresNew(Long id){
}
相應的實現代碼如下:
if (isExistingTransaction()) {
suspend(existing);
try {
return createNewTransaction();
} catch (exception) {
resumeAfterBeginException();
throw exception;
}
}
return createNewTransaction();
NESTED
NESTED顧名思義,是嵌套的Transaction,Spring首先檢查transaction是否存在,如果存在則創建一個savepoint,如果我們的程序拋出異常的時候,transaction將會回滾到該savepoint。如果沒有transaction,NESTED的表現和REQUIRED一樣。
我們看下怎么使用:
@Transactional(propagation = Propagation.NESTED)
public void deleteBookWithNested(Long id){
}
Transaction的隔離級別
隔離級別就是我們之前提到的原子性,一致性,隔離性和持久性。隔離級別描述了改動對其他並發者的可見程度。
隔離級別主要是為了防止下面3個並發過程中可能出現的問題:
- 臟讀: 讀取一個transaction還沒有提交的change
- 不可重復讀:在一個transaction修改數據庫中的某行數據時,另外一個transaction多次讀取同一行數據,獲取到的不同的值。
- 幻讀: 在一個transaction添加或者刪除數據庫的數據時,另外一個transaction做范圍查詢,獲得了不同的數據行數。
READ_UNCOMMITTED
READ_UNCOMMITTED是隔離級別中最低的級別。這個級別下,並發的3個問題都可能出現。
我們這樣使用:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteBookWithReadUncommitted(Long id){
}
READ_COMMITTED
READ_COMMITTED可以防止臟讀。
我們看下代碼:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void deleteBookWithReadCommitted(Long id){
}
REPEATABLE_READ
REPEATABLE_READ可以防止臟讀和不可重復讀。
使用的代碼如下:
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void deleteBookWithRepeatableRead(Long id){
}
SERIALIZABLE
SERIALIZABLE是最嚴格的基本,可以防止臟讀,不可重復讀和幻讀。
我們看下怎么使用:
@Transactional(isolation = Isolation.SERIALIZABLE)
public void deleteBookWithSerializable(Long id){
}
本文的例子可以參考https://github.com/ddean2009/learn-springboot2/tree/master/springboot-transaction
更多教程請參考 flydean的博客