事務
Spring 事務的本質其實就是資料庫對事務的支援,沒有資料庫的事務支援,Spring 是無法提供事務功能的。Spring 只提供統一事務管理介面,具體實現都是由各資料庫自己實現,資料庫事務的提交和回滾是透過資料庫自己的事務機制實現。
23.Spring 事務的種類?
在 Spring 中,事務管理可以分為兩大類:宣告式事務管理和程式設計式事務管理。
Spring事務分類
介紹一下程式設計式事務管理?
程式設計式事務可以使用 TransactionTemplate 和 PlatformTransactionManager 來實現,需要顯式執行事務。允許我們在程式碼中直接控制事務的邊界,透過程式設計方式明確指定事務的開始、提交和回滾。
public class AccountService {
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public void transfer(final String out, final String in, final Double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// 轉出
accountDao.outMoney(out, money);
// 轉入
accountDao.inMoney(in, money);
}
});
}
}
在上面的程式碼中,我們使用了 TransactionTemplate 來實現程式設計式事務,透過 execute 方法來執行事務,這樣就可以在方法內部實現事務的控制。
介紹一下宣告式事務管理?
宣告式事務是建立在 AOP 之上的。其本質是透過 AOP 功能,對方法前後進行攔截,將事務處理的功能編織到攔截的方法中,也就是在目標方法開始之前啟動一個事務,在目標方法執行完之後根據執行情況提交或者回滾事務。
相比較程式設計式事務,優點是不需要在業務邏輯程式碼中摻雜事務管理的程式碼, Spring 推薦透過 @Transactional 註解的方式來實現宣告式事務管理,也是日常開發中最常用的。
不足的地方是,宣告式事務管理最細粒度只能作用到方法級別,無法像程式設計式事務那樣可以作用到程式碼塊級別。
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
@Transactional
public void transfer(String out, String in, Double money) {
// 轉出
accountDao.outMoney(out, money);
// 轉入
accountDao.inMoney(in, money);
}
}
說說兩者的區別?
- 程式設計式事務管理:需要在程式碼中顯式呼叫事務管理的 API 來控制事務的邊界,比較靈活,但是程式碼侵入性較強,不夠優雅。
- 宣告式事務管理:這種方式使用 Spring 的 AOP 來宣告事務,將事務管理程式碼從業務程式碼中分離出來。優點是程式碼簡潔,易於維護。但缺點是不夠靈活,只能在預定義的方法上使用事務。
- Java 面試指南(付費)收錄的京東同學 10 後端實習一面的原題:Spring 事務怎麼實現的
- Java 面試指南(付費)收錄的農業銀行面經同學 7 Java 後端面試原題:Spring 如何保證事務
24.說說 Spring 的事務隔離級別?
好,事務的隔離級別定義了一個事務可能受其他併發事務影響的程度。SQL 標準定義了四個隔離級別,Spring 都支援,並且提供了對應的機制來配置它們,定義在 TransactionDefinition 介面中。
①、ISOLATION_DEFAULT:使用資料庫預設的隔離級別(你們愛咋咋滴 😁),MySQL 預設的是可重複讀,Oracle 預設的讀已提交。
②、ISOLATION_READ_UNCOMMITTED:讀未提交,允許事務讀取未被其他事務提交的更改。這是隔離級別最低的設定,可能會導致“髒讀”問題。
③、ISOLATION_READ_COMMITTED:讀已提交,確保事務只能讀取已經被其他事務提交的更改。這可以防止“髒讀”,但仍然可能發生“不可重複讀”和“幻讀”問題。
④、ISOLATION_REPEATABLE_READ:可重複讀,確保事務可以多次從一個欄位中讀取相同的值,即在這個事務內,其他事務無法更改這個欄位,從而避免了“不可重複讀”,但仍可能發生“幻讀”問題。
⑤、ISOLATION_SERIALIZABLE:序列化,這是最高的隔離級別,它完全隔離了事務,確保事務序列化執行,以此來避免“髒讀”、“不可重複讀”和“幻讀”問題,但效能影響也最大。
- Java 面試指南(付費)收錄的華為面經同學 8 技術二面面試原題:Spring 中的事務的隔離級別,事務的傳播行為?
- Java 面試指南(付費)收錄的小米麵經同學 E 第二個部門 Java 後端技術一面面試原題:spring 的隔離機制,預設是哪一種
25.Spring 的事務傳播機制?
事務的傳播機制定義了在方法被另一個事務方法呼叫時,這個方法的事務行為應該如何。
Spring 提供了一系列事務傳播行為,這些傳播行為定義了事務的邊界和事務上下文如何在方法呼叫鏈中傳播。
6種事務傳播機制
- REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。Spring 的預設傳播行為。
- SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務方式執行。
- MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。
- REQUIRES_NEW:總是啟動一個新的事務,如果當前存在事務,則將當前事務掛起。
- NOT_SUPPORTED:總是以非事務方式執行,如果當前存在事務,則將當前事務掛起。
- NESTED:如果當前存在事務,則在巢狀事務內執行。如果當前事務不存在,則行為與 REQUIRED 一樣。巢狀事務是一個子事務,它依賴於父事務。父事務失敗時,會回滾子事務所做的所有操作。但子事務異常不一定會導致父事務的回滾。
事務傳播機制是使用 ThreadLocal 實現的,所以,如果呼叫的方法是在新執行緒中的,事務傳播會失效。
Spring 預設的事務傳播行為是 PROPAFATION_REQUIRED,即如果多個 ServiceXmethodX()
都工作在事務環境下,且程式中存在呼叫鏈 Service1method1()->Service2method2()->Service3method3()
,那麼這 3 個服務類的 3 個方法都透過 Spring 的事務傳播機制工作在同一個事務中。
protected 和 private 加事務會生效嗎
在 Spring 中,只有透過 Spring 容器的 AOP 代理呼叫的公開方法(public method)上的@Transactional
註解才會生效。
如果在 protected、private 方法上使用@Transactional
,這些事務註解將不會生效,原因:Spring 預設使用基於 JDK 的動態代理(當介面存在時)或基於 CGLIB 的代理(當只有類時)來實現事務。這兩種代理機制都只能代理公開的方法。
- Java 面試指南(付費)收錄的京東同學 10 後端實習一面的原題:事務的傳播機制
- Java 面試指南(付費)收錄的小米春招同學 K 一面面試原題:事務傳播,protected 和 private 加事務會生效嗎,還有那些不生效的情況
- Java 面試指南(付費)收錄的華為面經同學 8 技術二面面試原題:Spring 中的事務的隔離級別,事務的傳播行為?
26.宣告式事務實現原理了解嗎?
Spring 的宣告式事務管理是透過 AOP(面向切面程式設計)和代理機制實現的。
第一步,在 Bean 初始化階段建立代理物件:
Spring 容器在初始化單例 Bean 的時候,會遍歷所有的 BeanPostProcessor 實現類,並執行其 postProcessAfterInitialization 方法。
在執行 postProcessAfterInitialization 方法時會遍歷容器中所有的切面,查詢與當前 Bean 匹配的切面,這裡會獲取事務的屬性切面,也就是 @Transactional
註解及其屬性值。
然後根據得到的切面建立一個代理物件,預設使用 JDK 動態代理建立代理,如果目標類是介面,則使用 JDK 動態代理,否則使用 Cglib。
第二步,在執行目標方法時進行事務增強操作:
當透過代理物件呼叫 Bean 方法的時候,會觸發對應的 AOP 增強攔截器,宣告式事務是一種環繞增強,對應介面為MethodInterceptor
,事務增強對該介面的實現為TransactionInterceptor
,類圖如下:
事務攔截器TransactionInterceptor
在invoke
方法中,透過呼叫父類TransactionAspectSupport
的invokeWithinTransaction
方法進行事務處理,包括開啟事務、事務提交、異常回滾等。
- Java 面試指南(付費)收錄的京東同學 10 後端實習一面的原題:Spring 事務怎麼實現的
27.宣告式事務在哪些情況下會失效?
宣告式事務的幾種失效的情況
1、@Transactional 應用在非 public 修飾的方法上
如果 Transactional 註解應用在非 public 修飾的方法上,Transactional 將會失效。
是因為在 Spring AOP 代理時,TransactionInterceptor (事務攔截器)在目標方法執行前後進行攔截,DynamicAdvisedInterceptor(CglibAopProxy 的內部類)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法會間接呼叫 AbstractFallbackTransactionAttributeSource 的 computeTransactionAttribute方法,獲取 Transactional 註解的事務配置資訊。
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
}
此方法會檢查目標方法的修飾符是否為 public,不是 public 則不會獲取 @Transactional 的屬性配置資訊。
2、@Transactional 註解屬性 propagation 設定錯誤
- TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務方式執行;錯誤使用場景:在業務邏輯必須執行在事務環境下以確保資料一致性的情況下使用 SUPPORTS。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:總是以非事務方式執行,如果當前存在事務,則掛起該事務。錯誤使用場景:在需要事務支援的操作中使用 NOT_SUPPORTED。
- TransactionDefinition.PROPAGATION_NEVER:總是以非事務方式執行,如果當前存在事務,則丟擲異常。錯誤使用場景:在應該在事務環境下執行的操作中使用 NEVER。
3、@Transactional 註解屬性 rollbackFor 設定錯誤
rollbackFor 用來指定能夠觸發事務回滾的異常型別。Spring 預設丟擲未檢查 unchecked 異常(繼承自 RuntimeException 的異常)或者 Error 才回滾事務,其他異常不會觸發回滾事務。
Spring預設支援的異常回滾
// 希望自定義的異常可以進行回滾
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)
若在目標方法中丟擲的異常是 rollbackFor 指定的異常的子類,事務同樣會回滾。
4、同一個類中方法呼叫,導致@Transactional 失效
開發中避免不了會對同一個類裡面的方法呼叫,比如有一個類 Test,它的一個方法 A,A 呼叫本類的方法 B(不論方法 B 是用 public 還是 private 修飾),但方法 A 沒有宣告註解事務,而 B 方法有。
則外部呼叫方法 A 之後,方法 B 的事務是不會起作用的。這也是經常犯錯誤的一個地方。
那為啥會出現這種情況呢?其實還是由 Spring AOP 代理造成的,因為只有事務方法被當前類以外的程式碼呼叫時,才會由 Spring 生成的代理物件來管理。
//@Transactional
@GetMapping("/test")
private Integer A() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
/**
* B 插入欄位為 3的資料
*/
this.insertB();
/**
* A 插入欄位為 2的資料
*/
int insert = cityInfoDictMapper.insert(cityInfoDict);
return insert;
}
@Transactional()
public Integer insertB() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("3");
cityInfoDict.setParentCityId(3);
return cityInfoDictMapper.insert(cityInfoDict);
}
這種情況是最常見的一種@Transactional 註解失效場景。
@Transactional
private Integer A() throws Exception {
int insert = 0;
try {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
cityInfoDict.setParentCityId(2);
/**
* A 插入欄位為 2的資料
*/
insert = cityInfoDictMapper.insert(cityInfoDict);
/**
* B 插入欄位為 3的資料
*/
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
如果 B 方法內部拋了異常,而 A 方法此時 try catch 了 B 方法的異常,那這個事務就不能正常回滾了,會丟擲異常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
- Java 面試指南(付費)收錄的小米春招同學 K 一面面試原題:事務傳播,protected 和 private 加事務會生效嗎,還有那些不生效的情況