13-SpringBoot之資料庫(四)——事務處理:隔離級別與傳播行為
SpringBoot之資料庫(四)——事務處理:隔離級別與傳播行為
- 1. 隔離級別(isolation)
- 2. 傳播行為(pragation)
- 2.1 REQUIRED(0)
- 2.2 SUPPORTS(1)
- 2.3 MANDATORY(2)
- 2.4 REQUIRES_NEW(3)
- 2.5 NOT_SUPPORTED(4)
- 2.6 NEVER(5)
- 2.7 NESTED(6)
- 3. @Transactional的自呼叫失效問題
在Spring中,資料庫事務是通過AOP技術來提供服務的。對於宣告式事務,是使用@Transactional進行標註的。在@Transactional允許配置許多事物的屬性,如事務的隔離級別與傳播行為。
1. 隔離級別(isolation)
資料庫標準提出了4種isolation事務隔離級別,分別為:未提交讀、讀寫提交、可重複讀和序列化。原始碼如下:
package org.springframework.transaction.annotation;
/***
* 隔離級別數字數字配置含義:
* -1:資料庫預設隔離級別
* 1:未提交讀
* 2:讀寫提交
* 4:可重複讀
* 8:序列化
*/
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
SpringBoot設定預設隔離級別配置:
spring:
datasource:
tomcat:
default-transaction-isolation: 2
# dbcp2:
# default-transaction-isolation: 2
1.1 未提交讀
未提交讀(READ_UNCOMMITTED)是最低的隔離級別,其含義是允許一個事物讀取另一個事物沒提交的資料。優點在於併發能力高,適合那些對資料一致性沒有要求而追求高併發的場景,最大缺點是出現髒讀。
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
例子講解:
T3時刻,因為採用未提交讀,所以事務2可以讀取事務1未提交的庫存資料為1,這裡當它扣減庫存後則資料為0,然後它提交了事務,庫存就變為了0 。,而事務1在T5時刻回滾事務,因為第一類丟失更新已經被克服,所以它不會將庫存回滾到2,那麼最後的結果就變為了0,這樣就出現了錯誤。
1.2 讀寫提交
讀寫提交(READ_COMMITTED),一個事務只能讀取另外一個事務已提交的資料,不能讀取未提交的資料。該級別克服了髒讀,但不可重複讀。
@Transactional(isolation = Isolation.READ_COMMITTED)
案例講解:
1.3 可重複讀
可重複讀(REPEATABLE_READ),目標是克服讀寫提交中出現的不可重複讀的現象,但會出現幻讀。
@Transactional(isolation = Isolation.REPEATABLE_READ)
案例講解:
1.4 序列化
序列化(SERIALIZABLE),是資料庫最高的隔離級別,它能夠完全保證資料的一致性,但效能降低了。
1.5 使用合理的隔離級別
隔離級別和可能發生的現象如下:
對於不同的資料庫,支援的隔離級別也不一樣:Oracle只能支援讀寫提交和序列化,而MySQL能夠支援4種,對於Oracle預設的隔離級別為讀寫提交,MySQL則是可重複讀。
2. 傳播行為(pragation)
Spring事務機制中對資料庫存在7種傳播行為,原始碼如下:
package org.springframework.transaction.annotation;
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
2.1 REQUIRED(0)
需要事務,它是預設傳播行為,如果當前存在事務,就沿用當前事務,否則新建一個事務執行子方法。
2.2 SUPPORTS(1)
支援事務,如果當前存在事務,就沿用當前事務,如果不存在,則繼續採用無事務的方式執行子方法。
2.3 MANDATORY(2)
必須使用事務,如果當前沒有事務,則會丟擲異常,如果存在當前事務,就沿用當前事務。
2.4 REQUIRES_NEW(3)
無論當前事務是否存在,都會建立新事務執行方法,這樣新事務就可以擁有新的鎖和隔離級別等特性,與當前事務相互獨立。
2.5 NOT_SUPPORTED(4)
不支援事務,當前存在事務時,將掛起事務,執行方法。
2.6 NEVER(5)
不支援事務,如果當前方法存在事務,則丟擲異常,否則繼續使用無事務機制執行。
2.7 NESTED(6)
在當前方法呼叫子方法時,如果子方法發生異常,只回滾子方法執行過的SQL,而不回滾當前方法的事務。
常用的傳播行為主要有三種:REQUIRED 、REQUIRES_NEW、 NESTED。
3. @Transactional的自呼叫失效問題
註解@transactional的底層實現是Spring AOP技術,而Spring AOP技術使用的是動態代理。這就意味著對於靜態(static)方法和非public方法,註解@Transactional是失效的。
自呼叫是指一個類的一個方法去呼叫自身另外一個方法的過程。在自呼叫的過程中,是類自身的呼叫,而不是代理物件去呼叫, 那麼就不會產生 AOP,這樣 Spring就不能把你的程式碼織入到約定的流程中。
為了克服這個問題,一方面可以寫兩個Service,用一個Service去呼叫另一個Service,這樣就是代理物件的呼叫。Spring才會將你的程式碼織入事務流程。另一方面,也可以從Spring IoC容器中獲取代理物件來啟用AOP。從Spring IoC容器中獲取代理物件的程式碼例項如下:
import com.springboot.web.dao.UserDao;
import com.springboot.web.model.User;
import com.springboot.web.service.UserService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class UserServiceImpl implements UserService, ApplicationContextAware {
@Autowired
UserDao userDao;
private ApplicationContext applicationContext;
//實現生命週期方法,設定Ioc容器
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public void createUsers(List<User> userList){
UserService userService = applicationContext.getBean(UserService.class);
for(User user : userList){
userService.createUser(user);
}
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
public void createUser(User user){
userDao.createUser(user);
}
}