1. 本地事務
商品新增功能非常復雜,商品管理微服務在service層中調用保存spu和sku相關的方法,為了保證數據的一致性,必然會使用事務。
在JavaEE企業級開發的應用領域,為了保證數據的完整性和一致性,必須引入數據庫事務的概念,所以事務管理是企業級應用程序開發中必不可少的技術。
咱們之前玩的事務都是本地事務。所謂本地事務,是指該事務僅在當前項目內有效。
1.1. 基本概念
事務的概念:事務是邏輯上一組操作,組成這組操作各個邏輯單元,要么一起成功,要么一起失敗。
事務的四個特性(ACID):
- 原子性(atomicity):“原子”的本意是“不可再分”,事務的原子性表現為一個事務中涉及到的多個操作在邏輯上缺一不可。事務的原子性要求事務中的所有操作要么都執行,要么都不執行。
- 一致性(consistency):“一致”指的是數據的一致,具體是指:所有數據都處于滿足業務規則的一致性狀態。一致性原則要求:一個事務中不管涉及到多少個操作,都必須保證事務執行之前數據是正確的,事務執行之后數據仍然是正確的。如果一個事務在執行的過程中,其中某一個或某幾個操作失敗了,則必須將其他所有操作撤銷,將數據恢復到事務執行之前的狀態,這就是回滾。
- 隔離性(isolation):在應用程序實際運行過程中,事務往往是并發執行的,所以很有可能有許多事務同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞。隔離性原則要求多個事務在并發執行過程中不會互相干擾。
- 持久性(durability):持久性原則要求事務執行完成后,對數據的修改永久的保存下來,不會因各種系統錯誤或其他意外情況而受到影響。通常情況下,事務對數據的修改應該被寫入到持久化存儲器中。
1.2. 隔離級別
事務并發引起一些讀的問題:
- 臟讀:一個事務可以讀取另一個事務未提交的數據
- 不可重復讀: 一個事務可以讀取另一個事務已提交的數據 單條記錄前后不匹配
- 虛讀(幻讀: 一個事務可以讀取另一個事務已提交的數據 讀取的數據前后多了點或者少了點
并發寫:使用mysql默認的鎖機制(獨占鎖)
解決讀問題:設置事務隔離級別
- read uncommitted(0)
- read committed(2)
- repeatable read(4)
- Serializable(8)
隔離級別越高,性能越低。
一般情況下:臟讀是不可允許的,不可重復讀和幻讀是可以被適當允許的。
1.3. 相關命令
查看全局事務隔離級別:SELECT @@global.tx_isolation
設置全局事務隔離級別:set global transaction isolation level read committed;
查看當前會話事務隔離級別:SELECT @@tx_isolation
設置當前會話事務隔離級別:set session transaction isolation level read committed;
查看mysql默認自動提交狀態:select @@autocommit
設置mysql默認自動提交狀態:set autocommit = 0;【不自動提交】
開啟一個事務:start transaction;
提交事務:commit
回滾事務: rollback
在事務中創建一個保存點:savepoint tx1
回滾到保存點:rollback to tx1
1.4. 傳播行為
事務的傳播行為不是jdbc規范中的定義。傳播行為主要針對實際開發中的問題
七種傳播行為:
REQUIRED 支持當前事務,如果不存在,就新建一個
SUPPORTS 支持當前事務,如果不存在,就不使用事務
MANDATORY 支持當前事務,如果不存在,拋出異常
REQUIRES_NEW 如果有事務存在,掛起當前事務,創建一個新的事務
NOT_SUPPORTED 以非事務方式運行,如果有事務存在,掛起當前事務
NEVER 以非事務方式運行,如果有事務存在,拋出異常
NESTED 如果當前事務存在,則嵌套事務執行(嵌套式事務)
- 依賴于JDBC3.0提供的SavePoint技術
- 刪除用戶 刪除訂單。在刪除訂單后,設置savePoint,執行刪除用戶。刪除訂單和刪除用戶在同一事務中,刪除用戶失敗,事務回滾savePoint,由用戶控制視圖提交還是回滾
這七種事務傳播機制最常用的就兩種:
REQUIRED:一個事務,要么成功,要么失敗
REQUIRES_NEW:兩個不同事務,彼此之間沒有關系。一個事務失敗了不影響另一個事務
1.4.1. 偽代碼練習
傳播行為偽代碼模擬:有a,b,c,d,e等5個方法,a中調用b,c,d,e方法的傳播行為在小括號中標出
a(required){ b(required); c(requires_new); d(required); e(requires_new); // a方法的業務 }
問題:
- a方法的業務出現異常,會怎樣?a,b,d回滾 c,e不回滾
- d方法出現異常,會怎樣?a,b,d回滾;c不回滾;e未執行
- e方法出現異常,會怎樣?a,b,d,e回滾 c不回滾,e方法出異常會上拋影響到上級方法
- b方法出現異常,會怎樣?a,b回滾 c,d,e未執行
加點難度:
a(required){ b(required){ f(requires_new); g(required) } c(requires_new){ h(requires_new) i(required) } d(required); e(requires_new); // a方法的業務 }
問題:
- a方法業務出異常?a,b,g,d回滾;f,c,h,i,e不回滾
- e方法出異常?e,a,b,g,d回滾;f,c,h,i不回滾
- d方法出異常?a,b,g,d回滾;f,c,h,i不回滾;e為執行
- h,i方法分別出異常?h,i,c,a,b,g回滾;f不回滾;d,e未執行
- i方法出異常?i,c,a,b,g回滾;f,h不回滾;d,e未執行
- f,g方法分別出異常?f,g,b,a回滾;c,h,i,d,e未執行
1.4.2. 改造商品新增代碼
現在商品保存的方法結構如下:
@Override public void bigSave(SpuVo spuVo) { /// 1.保存spu相關 // 1.1. 保存spu基本信息 spu_info Long spuId = saveSpu(spuVo); // 1.2. 保存spu的描述信息 spu_info_desc saveSpuDesc(spuVo, spuId); // 1.3. 保存spu的規格參數信息 saveBaseAttr(spuVo, spuId); /// 2. 保存sku相關信息 saveSku(spuVo, spuId); } /** * 保存sku相關信息及營銷信息 * @param spuInfoVO */ private void saveSku(SpuVo spuVo, Long spuId) { 。。。 } /** * 保存spu基本屬性信息 * @param spuInfoVO */ private void saveBaseAttr(SpuVo spuVo, Long spuId) { 。。。 } /** * 保存spu描述信息(圖片) * @param spuInfoVO */ private void saveSpuDesc(SpuVo spuVo, Long spuId) { 。。。 } /** * 保存spu基本信息 * @param spuInfoVO */ private void saveSpu(SpuVo spuVo) { 。。。 }
為了測試事務傳播行為,我們在SpuInfoService接口中把saveSkuInfoWithSaleInfo、saveBaseAttrs、saveSpuDesc、saveSpuInfo聲明為service接口方法。
public interface SpuInfoService extends IService<SpuInfoEntity> { PageVo queryPage(QueryCondition params); PageVo querySpuInfo(QueryCondition condition, Long catId); void saveSpuInfoVO(SpuInfoVO spuInfoVO); void saveSku(SpuVo spuVo, Long spuId); void saveBaseAttr(SpuVo spuVo, Long spuId); void saveSpuDesc(SpuVo spuVo, Long spuId); Long saveSpu(SpuVo spuVo); }
再把SpuInfoServiceImpl實現類的對應方法改成public:
1.4.3. 測試1:同一service + requires_new
springboot 1.x使用事務需要在引導類上添加**@EnableTransactionManagement注解開啟事務支持**
springboot 2.x可直接使用**@Transactional**玩事務,傳播行為默認是REQUIRED
添加事務:
這時,在保存商品的主方法中制造異常:
由于保存商品描述方法使用的是requires_new,spu應該會回滾,spu_desc應該保存成功。
清空pms_spu_desc表,再添加一個spu保存。
結果pms_spu_desc表中依然沒有數據。
但是控制臺打印了新增pms_spu_desc表的sql語句:
說明saveSpuDesc方法的事務回滾了,也就是說該方法配置的事務傳播機制沒有生效。
解決方案:
把service方法放到不同的service中使用動態代理對象調用該方法
1.4.4. 測試2:不同service + requires_new
把saveSpuDesc方法放到SpuDescService中:
在實現類中實現該方法,可以把之前的實現copy過來:
改造SpuServiceImpl中保存商品的方法,調用SpuDescServiceImpl的saveSpuDesc方法:
再次重啟gmall-pms,雖然控制臺依然報錯,但是數據可以保存成功,說明沒有在一個事務中。
為什么測試1的事務傳播行為沒有生效,而測試2的事務傳播行為生效了?
spring的事務是聲明式事務,而聲明式事務的本質是Spring AOP,SpringAOP的本質是動態代理。
事務要生效必須是代理對象在調用。
測試1:通過this調用同一個service中的方法,this是指service實現類對象本身,不是代理對象,就相當于方法中的代碼粘到了大方法里面,相當于還是一個方法。
測試2:通過其他service對象(spuDescService)調用,這個service對象本質是動態代理對象
接下來debug,打個斷點看看:
spuDescService:
this:
1.4.5. 在同一個service中使用傳播行為
只需要把測試1中的this.方法名()
替換成this代理對象.方法名()
即可。
問題是怎么在service中獲取當前類的代理對象?
在類中獲取代理對象分三個步驟:
- 導入aop的場景依賴:spring-boot-starter-aop
- 開啟AspectJ的自動代理,同時要暴露代理對象:@EnableAspectJAutoProxy(exposeProxy=true)
- 獲取代理對象:SpuInfoService proxy = (SpuInfoService) AopContext.currentProxy();
具體如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
重啟后測試:先清空pms_spu_info_desc表中數據
表中數據新增成功,說明saveSpuDesc方法走的是自己的事務,傳播行為生效了。
debug可以看到,spuInfoService是一個代理對象。
1.5. 回滾策略
事務很重要的另一個特征是程序出異常時,會回滾。但并不是所有的異常都會回滾。
默認情況下的回滾策略:
- 運行時異常:不受檢異常,沒有強制要求try-catch,都會回滾。例如:ArrayOutOfIndex,OutofMemory,NullPointException
- 編譯時異常:受檢異常,必須處理,要么try-catch要么throws,都不回滾。例如:FileNotFoundException
可以通過@Transactional注解的下面幾個屬性改變回滾策略:
- rollbackFor:指定的異常必須回滾
- noRollbackFor:發生指定的異常不用回滾
1.5.1. 測試編譯時異常不回滾
在商品保存方法中制造一個編譯時異常:
重啟測試,注意pms_spu表中數據:
控制臺報異常:
pms_spu表中的數據新增成功了。
也就證明了編譯時異常不回滾。
1.5.2. 定制回滾策略
經過剛才的測試,我們知道:
ArithmeticException異常(int i = 1/0)會回滾FileNotFoundException異常(new FileInputStream(“xxxx”))不回滾
接下來我們來改變一下這個策略:
測試:
FileNotFoundException:在程序中添加new FileInputStream(“xxxx”),然后測試。
還是id還是17,說明回滾了(回滾也會占用id=18)
ArithmeticException:在程序中添加int i = 1/0; 然后測試。
id是19,說明沒有回滾。
1.6. 超時事務
@Transactional注解,還有一個屬性是timeout超時時間,單位是秒。
timeout=3:是指第一個sql開始執行到最后一個sql結束執行之間的間隔時間。
即:超時時間(timeout)是指數據庫超時,不是業務超時。
改造之前商品保存方法:SpuInfoServiceImpl類中
重啟測試:控制臺出現事務超時異常
1.7. 只讀事務
@Transactional注解最后一個屬性是只讀事務屬性
如果一個方法標記為readOnly=true事務,則代表該方法只能查詢,不能增刪改。readOnly默認為false
給商品新增的事務標記為只讀事務:
測試:
到此這篇關于spring boot本地事務@Transactional參數詳解的文章就介紹到這了,更多相關spring boot事務Transactional內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/weixin_44743245/article/details/120631318