1. 程式人生 > >13-SpringBoot之資料庫(四)——事務處理:隔離級別與傳播行為

13-SpringBoot之資料庫(四)——事務處理:隔離級別與傳播行為

SpringBoot之資料庫(四)——事務處理:隔離級別與傳播行為


在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);
    }
    
}