技術(shù)面:Spring (事務(wù)傳播機(jī)制、事務(wù)失效的原因、BeanFactory和FactoryBean的關(guān)系)
Spring的事務(wù)傳播機(jī)制
什么是Spring事務(wù)傳播機(jī)制
Spring的事務(wù)傳播機(jī)制,主要是用于控制多個事務(wù)方法相互調(diào)用時的事務(wù)行為。
在后端復(fù)雜的業(yè)務(wù)場景中,多個事務(wù)之間的調(diào)用可能會導(dǎo)致事務(wù)的不一致,例如:數(shù)據(jù)重復(fù)提交,數(shù)據(jù)丟失等問題,使用事務(wù)傳播機(jī)制可以避免這些問題的發(fā)生,從而保證事務(wù)的一致性和數(shù)據(jù)的完整性。
Spring的事務(wù)規(guī)定了7種傳播行為
Spring 通過 @Transactional 注解的 propagation 屬性來設(shè)置傳播級別
- Propagation.REQUIRED (默認(rèn))
- 含義:如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則創(chuàng)建一個新的事務(wù)。
- 特點(diǎn):這是最常用、且Spring默認(rèn)的傳播行為。適用于絕大多數(shù)業(yè)務(wù)場景。
- 回滾:內(nèi)部方法拋出異常未被捕獲,會導(dǎo)致整個事務(wù)(包括外部方法的操作)回滾。
- Propagation.REQUIRES_NEW
- 含義:無論當(dāng)前是否存在事務(wù),都會創(chuàng)建一個新的事務(wù),并將當(dāng)前事務(wù)(如果存在)掛起。
- 特點(diǎn):創(chuàng)建的是一個完全獨(dú)立的事務(wù),有自己的提交和回滾邊界。
- 回滾:內(nèi)部新事務(wù)回滾,不影響外部事務(wù)(如果外部事務(wù)正常提交)。
外部事務(wù)回滾,會將內(nèi)部新事務(wù)也回滾(因?yàn)橥獠渴聞?wù)的回滾會恢復(fù)到調(diào)用REQUIRES_NEW方法之前的狀態(tài))。 - 場景:記錄日志、發(fā)送通知、審計等需要獨(dú)立提交的場景。
- Propagation.NESTED
- 含義:如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行(基于數(shù)據(jù)庫的 Savepoint 機(jī)制);如果不存在事務(wù),則創(chuàng)建一個新事務(wù)。
- 特點(diǎn):不是創(chuàng)建一個真正的新事務(wù),而是在當(dāng)前事務(wù)中設(shè)置一個保存點(diǎn)(Savepoint)。它可以在不破壞外部事務(wù)的情況下進(jìn)行部分回滾。
- 回滾:內(nèi)部嵌套事務(wù)回滾,只回滾到保存點(diǎn),不影響外部事務(wù)已做的其他操作。外部事務(wù)回滾,會回滾整個事務(wù),包括嵌套事務(wù)的操作。與
REQUIRES_NEW區(qū)別:NESTED是“子事務(wù)”,依賴于外部事務(wù);REQUIRES_NEW是“獨(dú)立事務(wù)”,與外部事務(wù)并列。
- Propagation.SUPPORTS
- 含義:如果當(dāng)前存在事務(wù),則加入該事務(wù);如果不存在事務(wù),則以非事務(wù)方式執(zhí)行。
- 特點(diǎn):對事務(wù)是“可有可無”的態(tài)度。
- 場景:適用于只讀操作或?qū)κ聞?wù)不敏感的方法。
- Propagation.NOT_SUPPORTED
- 含義:以非事務(wù)方式執(zhí)行操作。如果當(dāng)前存在事務(wù),則將當(dāng)前事務(wù)掛起。
- 特點(diǎn):強(qiáng)制方法不運(yùn)行在事務(wù)中,可以提高性能。
- 場景:執(zhí)行一些耗時長、不需要事務(wù)保證的操作。
- Propagation.NEVER
- 含義:以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。
- 特點(diǎn):強(qiáng)制禁止在事務(wù)中執(zhí)行此方法。
- 場景:某些特定操作明確要求不能在事務(wù)上下文中運(yùn)行。
- Propagation.MANDATORY
- 含義:方法必須在一個已存在的事務(wù)中執(zhí)行,如果當(dāng)前沒有事務(wù),則拋出異常。
- 特點(diǎn):強(qiáng)制要求調(diào)用方必須提供一個事務(wù)。
- 場景:用于那些必須作為更大事務(wù)一部分才能保證一致性的操作
面試題
面試官問:一個長的事務(wù)方法a,在讀寫分離的情況下,里面既有讀庫操作,也有寫庫操作,再調(diào)用個讀庫方法b,方法b該用什么傳播機(jī)制呢?
答:這種情況,讀方法如果是最后一步,直接not_supported就行了,避免讀報錯導(dǎo)致數(shù)據(jù)回滾。如果是中間步驟,最好還是要required,因?yàn)楫惓J⌒枰貪L一下。
例如:A B C三個操作,C就是最后一步,B就是中間步驟如果一個讀操作在中間(如B操作)失敗了,那么就需要讓A做回滾,因?yàn)镃還沒執(zhí)行,所以A必須回滾才能保證一致性。
Spring事務(wù)失效可能是哪些原因
首先,容易造成事務(wù)失效的方式是通過@Transactional注解方式的聲明式事務(wù)。
@Transactional是基于Spring的AOP來實(shí)現(xiàn)的,而AOP機(jī)制又是基于動態(tài)代理實(shí)現(xiàn)的,如果代理失效那么事務(wù)也就失效了。
Spring事務(wù)失效的場景
AOP代理失效
@Transactional應(yīng)用在非public方法上
@Service
public class UserService {
@Transactional
private void updateUserData() { // private方法
// ...
}
}
由于代理機(jī)制會為 public 方法創(chuàng)建攔截器,事務(wù)可以正常生效。而非public得方法,JDK代理是不會創(chuàng)建攔截器的,雖然CGLIB可能支持,但行為不一致,不保證生效。
因此在使用時還是強(qiáng)烈建議放到public方法上。
類內(nèi)部的調(diào)用,類內(nèi)部方法自調(diào)用,內(nèi)部類方法調(diào)用
@Service
public class UserService {
public void businessMethod() {
// 1. 執(zhí)行一些業(yè)務(wù)邏輯
// 2. 調(diào)用本類的事務(wù)方法
this.transactionalMethod(); // 自調(diào)用,事務(wù)失效
}
@Transactional
public void transactionalMethod() {
// 數(shù)據(jù)庫操作
}
}
public class OuterClass{
private class InnerClass {
@Transactional
public void doSomething() {
System.out.println("Doing something in inner class...");
}
}
public void invokeInnerClassMethod() {
InnerClass innerclass = new InnerClass();
innerclass.doSomething();//調(diào)用內(nèi)部類方法,事務(wù)失效
}
}
在對象內(nèi)部調(diào)用其他方法,就會用對象直接調(diào)用了,而不是用代理對象,因此代理會失效。
static、final方法
由于static方法是屬于類級別的對象,所以代理對象無法代理,因此AOP也是無效的,因此@Transactional修飾這種方法時,事務(wù)也是會失效的。
final方法,是固定形式,而AOP的代理是通過子類或?qū)崿F(xiàn)接口來實(shí)現(xiàn)的,final方法無法被子類覆蓋,也無法通過實(shí)現(xiàn)類覆蓋。因此如果將@Transactional修飾這種方法時,事務(wù)也是會失效的。
不存在代理
沒有使用Spring管理bean,因此也就不會存在使用AOP來創(chuàng)建代理對象來保證事務(wù)。
@Transactional配置錯誤
@Transactional的propagation屬性配置錯誤
public class UserService{
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedMethod() {
// 此方法不會運(yùn)行在事務(wù)中
}
}
不同的Propagation屬性決定了事務(wù)的創(chuàng)建和參與方式。例如:Propagation.NOT_SUPPORTED或Propagation.NEVER會掛起或拒絕當(dāng)前事務(wù)。
@Transactional的rollbackFor設(shè)置錯誤
public class UserService{
@Transactional(rollbackFor = FileNotFoundException.class)
public void someMethod(){
// 拋出 IOException
throw new IOException("文件讀取失敗");
// 事務(wù)不會回滾
}
}
rollbackFor配置的異常類型需和方法拋出的異常一致,事務(wù)才會進(jìn)行回滾。改成使用@Transactional(rollbackFor = Exception.class)或@Transactional(rollbackFor = IOException.class)即可。
@Transactional注解引用來源錯誤
有時候,在寫代碼的時候,由于手快也沒有注意@Transactional注解的引用來源,直接就用了,等出現(xiàn)問題的時候,排查了很久發(fā)現(xiàn)寫的都沒問題,但是還是不生效,然后找別人來幫你看,他上來就看了一下你用的@Transactional,發(fā)現(xiàn)并不是Spring中的,而是其他什么地方的,比如javax.transaction.Transactional ,這樣也會導(dǎo)致事務(wù)失效。
沒有啟用事務(wù)管理
- 原因:忘記在Spring配置中啟用事務(wù)管理。
- 解決方案:
在Java配置中添加@EnableTransactionManagement。
在XML配置中添加<tx:annotation-driven />。
異常被捕獲
public class UserService{
@Transactional(rollbackFor = Exception.class)
public void doSomething() {
try {
// doSomething...
}catch (Exception e){
System.out.println("Exception:"+e);
}
}
}
異常被捕獲后,不會拋出,也就走不到rollbackFor這樣也就不會進(jìn)行回滾了。
在多線程環(huán)境下使用了聲明式事務(wù)
@Transactional的事務(wù)管理使用的是ThreadLocal來存儲事務(wù)上下文,ThreadLocal存儲的變量是線程隔離的,因此每個線程都有自己的事務(wù)上下文副本。所以Spring的聲明式事務(wù)在多線程環(huán)境下會失效的風(fēng)險。
數(shù)據(jù)庫引擎不支持事務(wù)
如果使用的數(shù)據(jù)庫表引擎不支持事務(wù)(如MySQL的MyISAM引擎),那么即使Spring配置了事務(wù),也無法回滾。
解決方案:確保數(shù)據(jù)庫表使用支持事務(wù)的引擎,如MySQL的InnoDB。
BeanFactory和FactoryBean的關(guān)系
從名字上看BeanFactory和FactoryBean看著很相似,但是實(shí)際上它倆沒什么關(guān)系,是完全不相關(guān)的兩個接口。
BeanFactory
BeanFactory就是Bean的工廠,是整個Spring的IOC其中的一部分,管理Bean的創(chuàng)建和生命周期。
BeanFactory提供了一系列的方法,可以讓我們獲取到具體的Bean實(shí)例。
你可能沒有直接用過BeanFactory,但是你肯定間接的使用或者看到過。
applicationContent.getBean(type);
applicationContent.getBean(name);
這些代碼通常用在一些測試用例,或者需要手動從IOC容器中獲取指定的Bean的時候使用。
通過上面的代碼使用示例也說明了,BeanFactory是IOC容器的一個接口,用來獲取Bean以及管理Bean的依賴注入和生命周期。
FactoryBean
FactoryBean本質(zhì)是一個特殊的Bean,用于定義一個工廠Bean,可以用來生成某些特定的Bean。
當(dāng)項(xiàng)目中定義了某一個Bean的時候,如果這個Bean實(shí)現(xiàn)了FactoryBean這個接口,那么使用這個Bean的時候,Spring的IOC容器不會直接返回這個Bean實(shí)例,而是返回FactoryBean的getObject()方法返回的實(shí)體對象。(獲取FactoryBean本身:需要在ID前加&符號(如&myFactoryBean))
// 定義一個FactoryBean
public class MyFactoryBean implements FactoryBean<MyObject> {
public MyObject getObject() {
return new MyObject(); // 返回實(shí)際對象
}
public Class<?> getObjectType() {
return MyObject.class;
}
public boolean isSingleton() {
return true;
}
}
// 使用
BeanFactory beanFactory = new DefaultListableBeanFactory();
// 注冊MyFactoryBean
beanFactory.registerSingleton("myFactoryBean", new MyFactoryBean());
// 獲取FactoryBean創(chuàng)建的對象
MyObject obj = (MyObject) beanFactory.getBean("myFactoryBean"); // 返回MyObject實(shí)例
// 獲取FactoryBean本身
FactoryBean<MyObject> factoryBean = (FactoryBean<MyObject>) beanFactory.getBean("&myFactoryBean");
FactoryBean常用于創(chuàng)建需要特殊初始化邏輯的Bean,如Spring AOP代理、JNDI數(shù)據(jù)源,kafka,Dubbo中都用FactoryBean與Spring做集成。
總結(jié)
| 特性 | BeanFactory | FactoryBean |
|---|---|---|
| 本質(zhì) | Spring容器核心接口 | 一個特殊Bean |
| 名稱含義 | Bean的工廠 | 工廠類型的Bean |
| 獲取對象 | 獲取Bean實(shí)例 | 獲取getObject()返回的對象 |
| 是否為容器 | 是 | 否(它是容器中的一個Bean) |
| 主要用途 | 管理所有Bean | 自定義特定Bean的創(chuàng)建邏輯 |
作者:紀(jì)莫
歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處。
限于本人水平,如果文章和代碼有表述不當(dāng)之處,還請不吝賜教。
歡迎掃描二維碼關(guān)注公眾號:Jimoer
文章會同步到公眾號上面,大家一起成長,共同提升技術(shù)能力。
聲援博主:如果您覺得文章對您有幫助,可以點(diǎn)擊文章右下角【推薦】一下。
您的鼓勵是博主的最大動力!


浙公網(wǎng)安備 33010602011771號