javascript
你了解过Spring支持的常用数据库事务传播属性和隔离级别吗?来一起看看吧!!!
上次偶然間看到這個知識點,發現自己有所欠缺,就來進行查漏補缺,沒法實在是卷的厲害啊。😭
那么不知道你對于Spring支持的常用數據庫事務傳播屬性和隔離級別了解的怎么樣呢?要不要一起復習復習勒😁
很喜歡一句話:“八小時內謀生活,八小時外謀發展”
共勉👩?💻
描述:進來先看看風景啦,要相信會有光的哦
Spring支持的常用數據庫事務傳播屬性和隔離級別
- 一、事務傳播屬性
- 前言:
- 概念:
- 二、事務傳播代碼演示
- 2.1、數據庫表:
- 2.2、代碼
- 2.3測試:
- 三、數據庫事務隔離級別
- 3.1、數據庫事務并發問題
- 3.2 數據庫隔離性
- 3.3、測試
- 四、自言自語
一、事務傳播屬性
前言:
對于數據庫事務ACID(原子性、一致性、隔離性、持久性)性質我想大家都是知道的,這里就不寫了😁
我們都知道用事務是為了保證數據庫的完整性,保證成批的 SQL 語句要么全部執行,要么全部不執行。
但是如果一個方法嵌套關聯著其他方法勒,這該怎么算呢?當前方法及關聯方法都有事務呢,或者只是其中某幾個有事務,該用誰的呢?
概念:
事務的傳播行為:一個方法運行在一個開啟了事務的方法上時,當前方法是使用原來的事務還是開啟一個新的事務。
通過 @Transaction 注解中 propagation 來設置事務傳播行為。其中
事務傳播行為總共有以下七種:
| REQUIRED | 業務方法需要在一個事務中運行。如果方法運行時,已經處在一個事務中,那么加入到該事務,否則為自己創建一個新的事務。(默認值) |
| NOT_SUPPORTED | 聲明方法不需要事務。如果方法沒有關聯到一個事務,容器不會為它開啟事務。如果方法在一個事務中被調用,該事務會被掛起,在方法調用結束后,原先的事務便會恢復執行。 應用場景:有數據操作處理(需要事務)+異步調用(不需要事務,掛起) |
| REQUIRESNEW | 不管是否存在事務,業務方法總會為自己發起一個新的事務。如果方法已經運行在一個事務中,則原有事務會被掛起,新的事務會被創建,直到方法執行結束,新事務才算結束,原先的事務才會恢復執行。 |
| MANDATORY | 該屬性指定業務方法只能在一個已經存在的事務中執行,業務方法不能發起自己的事務。如果業務方法在沒有事務的環境下調用,容器就會拋出例外。 |
| SUPPORTS | 這一事務屬性表明,如果業務方法在某個事務范圍內被調用,則方法成為該事務的一部分。如果業務方法在事務范圍外被調用,則方法在沒有事務的環境下執行。 |
| NEVER | 指定業務方法絕對不能在事務范圍內執行。如果業務方法在某個事務中執行,容器會拋出例外,只有業務方法沒有關聯到任何事務,才能正常執行。 例:應用于報表統計程序 |
| NESTED | 如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, 則按REQUIRED屬性執行.啟用一個新的事務, 這個事務擁有多個可以回滾的保存點。內部事務的回滾不會對外部事務造成影響。它只對DataSourceTransactionManager事務管理器起效 |
下面寫了一個小demo來讓理解更加快捷一些哈。
二、事務傳播代碼演示
2.1、數據庫表:
注意:account表中 balance字段是設置為無符號的(即不能為負數)。
2.2、代碼
項目就是普通Spring項目
模擬的是買書的一個過程,賬戶余額不足,但是一次買多本的情況,一起付款。
在其中再測試事務傳播行為的不同,來看數據的變化。
初始代碼:
public interface CashierService {void checkout(int userId, List<Integer> isbns); } @Service public class CashierServiceImpl implements CashierService {@AutowiredBookShopService bookShopService;@Transactional@Overridepublic 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 {@AutowiredBookShopMapper bookShopMapper;@Transactional@Overridepublic 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.1、數據庫事務并發問題
假設現在有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操作)
3.2 數據庫隔離性
數據庫事務的隔離性: 數據庫系統必須具有隔離并發運行各個事務的能力, 使它們不會相互影響, 避免各種并發問題.
一個事務與其他事務隔離的程度稱為隔離級別. 數據庫規定了多種事務隔離級別, 不同隔離級別對應不同的干擾程度, 隔離級別越高, 數據一致性就越好, 但并發性越弱
在代碼中,我們可以通過
數據庫提供了4種隔離級別:
| Read uncommitted (讀未提交) | 有 | 有 | 有 |
| Read committed(讀已提交) | 無 | 有 | 有 |
| Repeatable read (重復讀) | 無 | 無 | 有 |
| Serializable(序列化) | 無 | 無 | 無 |
- Oracle 默認的事務隔離級別為: READ COMMITED ,Oracle 支持的 2 種事務隔離級別:READ COMMITED, SERIALIZABLE.
- Mysql 默認的事務隔離級別為: REPEATABLE READ,Mysql 支持 4 種事務隔離級別.
3.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)@Overridepublic 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的默認事務隔級別是可重復讀,意思在這同一個事務中,可以重復讀。
注:因為這是直接修改數據庫,其操作行為并不可取,此處只是為了模擬。其結果有時也非一定準確。
四、自言自語
每天進步一點點,那么很快就可以進步很多。
你好,我是博主寧在春,下篇文章再見。😁😛
總結
以上是生活随笔為你收集整理的你了解过Spring支持的常用数据库事务传播属性和隔离级别吗?来一起看看吧!!!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM中垃圾回收相关算法 - 值得了解一
- 下一篇: SpringBoot整合Minio 项目