Spring支持的經常使用數據庫事務傳播屬性和隔離級別

這是我參與8月更文挑戰的第7天,活動詳情查看:8月更文挑戰java

上次偶然間看到這個知識點,發現本身有所欠缺,就來進行查漏補缺,無法實在是卷的厲害啊。😭mysql

那麼不知道你對於Spring支持的經常使用數據庫事務傳播屬性和隔離級別瞭解的怎麼樣呢?要不要一塊兒複習複習勒😁web

很喜歡一句話:「八小時內謀生活,八小時外謀發展」sql

共勉👩‍💻數據庫

描述:進來先看看風景啦,要相信會有光的哦markdown

1、事務傳播屬性

前言:

對於數據庫事務ACID(原子性、一致性、隔離性、持久性)性質我想你們都是知道的,這裏就不寫了😁併發

咱們都知道用事務是爲了保證數據庫的完整性,保證成批的 SQL 語句要麼所有執行,要麼所有不執行。oracle

可是若是一個方法嵌套關聯着其餘方法勒,這該怎麼算呢?當前方法及關聯方法都有事務呢,或者只是其中某幾個有事務,該用誰的呢?app

概念:

事務的傳播行爲:一個方法運行在一個開啓了事務的方法上時,當前方法是使用原來的事務仍是開啓一個新的事務。異步

經過 @Transaction 註解中 propagation 來設置事務傳播行爲。其中

事務傳播行爲總共有如下七種:

傳播屬性 描述
REQUIRED 業務方法須要在一個事務中運行。若是方法運行時,已經處在一個事務中,那麼加入到該事務,不然爲本身建立一個新的事務。(默認值)
NOT_SUPPORTED 聲明方法不須要事務。若是方法沒有關聯到一個事務,容器不會爲它開啓事務。若是方法在一個事務中被調用,該事務會被掛起,在方法調用結束後,原先的事務便會恢復執行。
應用場景:有數據操做處理(須要事務)+異步調用(不須要事務,掛起)
REQUIRESNEW 不論是否存在事務,業務方法總會爲本身發起一個新的事務。若是方法已經運行在一個事務中,則原有事務會被掛起,新的事務會被建立,直到方法執行結束,新事務纔算結束,原先的事務纔會恢復執行。
MANDATORY 該屬性指定業務方法只能在一個已經存在的事務中執行,業務方法不能發起本身的事務。若是業務方法在沒有事務的環境下調用,容器就會拋出例外。
SUPPORTS 這一事務屬性代表,若是業務方法在某個事務範圍內被調用,則方法成爲該事務的一部分。若是業務方法在事務範圍外被調用,則方法在沒有事務的環境下執行。
NEVER 指定業務方法絕對不能在事務範圍內執行。若是業務方法在某個事務中執行,容器會拋出例外,只有業務方法沒有關聯到任何事務,才能正常執行。
例:應用於報表統計程序
NESTED 若是一個活動的事務存在,則運行在一個嵌套的事務中. 若是沒有活動事務, 則按REQUIRED屬性執行.啓用一個新的事務, 這個事務擁有多個能夠回滾的保存點。內部事務的回滾不會對外部事務形成影響。它只對DataSourceTransactionManager事務管理器起效

下面寫了一個小demo來讓理解更加快捷一些哈。

2、事務傳播代碼演示

2.一、數據庫表:

注意:account表中 balance字段是設置爲無符號的(即不能爲負數)。

在這裏插入圖片描述

2.二、代碼

項目就是普通Spring項目

模擬的是買書的一個過程,帳戶餘額不足,可是一次買多本的狀況,一塊兒付款。

在其中再測試事務傳播行爲的不一樣,來看數據的變化。

初始代碼:

public interface CashierService {
    void checkout(int userId, List<Integer> isbns);
}
複製代碼
@Service
public class CashierServiceImpl implements CashierService {

    @Autowired
    BookShopService bookShopService;

    @Transactional
    @Override
    public void checkout(int userId, List<Integer> isbns) {
        for (Integer isbn : isbns) {
            // 調用bookShopService 買書的方法
            bookShopService.purchase(userId, isbn);
        }
    }
}
複製代碼
public interface BookShopService {
    void purchase(int userId,int isbn);
}
複製代碼
@Service
public class BookShopServiceImpl implements BookShopService {
    @Autowired
    BookShopMapper bookShopMapper;

    @Transactional
    @Override
    public void purchase(int userId, int isbn) {
        // 獲取要買的圖書
        double bookPrice = bookShopMapper.getBookPriceByIsbn(isbn);
        // 更新圖書的庫存
        bookShopMapper.updateBootStock(isbn);
        // 更新用戶的餘額
        bookShopMapper.updateAccountBalance(userId,bookPrice);
    }
}
複製代碼

mapper層代碼

@Mapper
@Repository
public interface BookShopMapper {

    @Select("select price from book where isbn=#{isbn}")
    double getBookPriceByIsbn(int isbn);

    @Update("update book_stock set stock=stock-1 where isbn=#{isbn}")
    void updateBootStock(int isbn);

    @Update("update account set balance=balance-#{bookPrice} where id=#{userId}")
    void updateAccountBalance(@Param("userId") int userId, double bookPrice);
}

複製代碼

2.3測試:

測試一:默認事務傳播行爲

咱們在void checkout(int userId, List<Integer> isbns) void purchase(int userId, int isbn)上都加了 @Transactional

目前帳戶爲 100元,兩本書的價格分別爲 60和50 ,由於咱們的付款過程是 使用循環 購買的,你說咱們會買到一本仍是一本都買不到呢?

@Autowired
CashierService cashierService;
@Test
void test(){
    List<Integer> isbns = new ArrayList<>();
    // 加購兩本書
    isbns.add(1001);
    isbns.add(1002);
    // 結帳
    cashierService.checkout(1,isbns);
}
複製代碼

答案固然是一本都買不到,由於@Transactional 註解 ,默認事務的傳播屬性是:REQUIRED,即業務方法須要在一個事務中運行。若是方法運行時,已經處在一個事務中,那麼加入到該事務,不然爲本身建立一個新的事務。因此實際上 void purchase(int userId, int isbn)其實和調用它的方法用的同一個事務。簡單畫個圖:

在這裏插入圖片描述

測試二:測試 -->REQUIRES_NEW屬性

其餘代碼未改變,僅在purchase 上的註解上加了點東西@Transactional(propagation = Propagation.REQUIRES_NEW).

REQUIRES_NEW: 不論是否存在事務,業務方法總會爲本身發起一個新的事務。若是方法已經運行在一個事務中,則原有事務會被掛起,新的事務會被建立,直到方法執行結束,新事務纔算結束,原先的事務纔會恢復執行。

你說說答案和上面是同樣的莫?😀

@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void purchase(int userId, int isbn) {
    // 獲取要買的圖書
    double bookPrice = bookShopMapper.getBookPriceByIsbn(isbn);
    // 更新圖書的庫存
    bookShopMapper.updateBootStock(isbn);
    // 更新用戶的餘額
    bookShopMapper.updateAccountBalance(userId,bookPrice);
}
複製代碼

答案是不同的,測試一 咱們實際上用的就是checkout上的事務,並無用到 purchase 的事務,從圖上也能看出來。

測試二它的事務傳播屬性 用 圖來說是這樣的啦:

在這裏插入圖片描述

因此是能夠買到一本書的。

還有不少,意思都解釋過了,沒有一一測完了。

在這裏插入圖片描述

3、數據庫事務隔離級別

3.一、數據庫事務併發問題

假設如今有A和B 兩個事務 併發執行。

1)髒讀:一個事務讀取到另外一事務未提交的更新新據

  • A 將某條記錄的 age 值 從 20修改成30
  • B 讀取了 A 更新後的值爲 30
  • A 回滾,age值回到20
  • B 讀取到的30 的值就是一個無效的值

2)不可重複讀: 同一事務中,屢次讀取同一數據返回的結果有所不一樣(針對的update操做)

  • A 讀取了 age 值 爲 20
  • B 將 age 值修改成30
  • A 再次讀去age 的值爲30

3)幻讀:一個事務讀取到另外一事務已提交的insert數據(針對的insert操做)

  1. A 讀取 學生表 中一部分數據
  2. B 向學生表中插入了 新的數據
  3. A 讀取 學生表時 多出了一些行

3.2 數據庫隔離性

數據庫事務的隔離性: 數據庫系統必須具備隔離併發運行各個事務的能力, 使它們不會相互影響, 避免各類併發問題.

一個事務與其餘事務隔離的程度稱爲隔離級別. 數據庫規定了多種事務隔離級別, 不一樣隔離級別對應不一樣的干擾程度, 隔離級別越高, 數據一致性就越好, 但併發性越弱

在代碼中,咱們能夠經過

數據庫提供了4種隔離級別:

髒讀 不可重複讀 幻讀
Read uncommitted (讀未提交)
Read committed(讀已提交)
Repeatable read (重複讀)
Serializable(序列化)
  • Oracle 默認的事務隔離級別爲: READ COMMITED ,Oracle 支持的 2 種事務隔離級別:READ COMMITED, SERIALIZABLE.
  • Mysql 默認的事務隔離級別爲: REPEATABLE READ,Mysql 支持 4 種事務隔離級別.

3.三、測試

: 模擬併發狀況。

1) 測試如下 mysql 的默認隔離級別:

public interface BookShopService {
    void purchase(int userId,int isbn);
    // 測試事務隔離級別
    void transactionIsolationTest(int isbn);
}
複製代碼
/** * propagation :用來設置事務傳播屬性 * Propagation.REQUIRED 默認事務傳播屬性 * * isolation :用來設置事務隔離級別 * Isolation.REPEATABLE_READ: mysql 默認事務隔離 * Isolation.READ_COMMITTED: oracle 默認事務隔離級別 */
    @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
    @Override
    public void transactionIsolationTest( int isbn) {
        // 獲取要買的圖書 
        double bookPrice = bookShopMapper.getBookPriceByIsbn(isbn);
        //此處應打上斷點,待代碼執行完上一句後,應手動將書的價格修改一下,看讀到的數據是多少
        System.out.println(bookPrice);

        double bookPrice2 = bookShopMapper.getBookPriceByIsbn(isbn);
        System.out.println(bookPrice2);
    }
複製代碼

測試代碼特別簡單,但由於我是手動模擬,得打斷點、debug啓動,

@Autowired
BookShopService bookShopService;
@Test
void transactionIsolationTest(){
    bookShopService.transactionIsolationTest(1001); 
}
複製代碼

當執行完第一個double bookPrice = bookShopMapper.getBookPriceByIsbn(isbn)語句時,應該去mysql 修改一下書的價格,這樣看一下結果。

在這裏插入圖片描述

在這裏插入圖片描述

這個時候再接着執行。看輸出什麼。

在這裏插入圖片描述

最後的結果仍然是50、50。由於mysql的默認事務隔級別是可重複讀,意思在這同一個事務中,能夠重複讀。

:由於這是直接修改數據庫,其操做行爲並不可取,此處只是爲了模擬。其結果有時也非必定準確。

4、自言自語

天天進步一點點,那麼很快就能夠進步不少。

你好,我是博主寧在春,下篇文章再見。😁😛

相關文章
相關標籤/搜索