1. Spring事務(wù)的基本原理
事務(wù)管理是應(yīng)用系統(tǒng)開(kāi)發(fā)中必不可少的一部分。Spring 為事務(wù)管理提供了豐富的功能支持。Spring 事務(wù)管理分為編碼式和聲明式的兩種方式。編程式事務(wù)指的是通過(guò)編碼方式實(shí)現(xiàn)事務(wù);聲明式事務(wù)基于 AOP,將具體業(yè)務(wù)邏輯與事務(wù)處理解耦。聲明式事務(wù)管理使業(yè)務(wù)代碼邏輯不受污染, 因此在實(shí)際使用中聲明式事務(wù)用的比較多。聲明式事務(wù)有兩種方式,一種是在配置文件(xml)中做相關(guān)的事務(wù)規(guī)則聲明,另一種是基于@Transactional 注解的方式。注釋配置是目前流行的使用方式,因此本文將著重介紹基于@Transactional 注解的事務(wù)管理。
使用@Transactional的相比傳統(tǒng)的我們需要手動(dòng)開(kāi)啟事務(wù),然后提交事務(wù)來(lái)說(shuō)。它提供如下方便
- 根據(jù)你的配置,設(shè)置是否自動(dòng)開(kāi)啟事務(wù)
- 自動(dòng)提交事務(wù)或者遇到異常自動(dòng)回滾
聲明式事務(wù)(@Transactional)基本原理如下:
- 配置文件開(kāi)啟注解驅(qū)動(dòng),在相關(guān)的類和方法上通過(guò)注解@Transactional標(biāo)識(shí)。
- spring 在啟動(dòng)的時(shí)候會(huì)去解析生成相關(guān)的bean,這時(shí)候會(huì)查看擁有相關(guān)注解的類和方法,并且為這些類和方法生成代理,并根據(jù)@Transaction的相關(guān)參數(shù)進(jìn)行相關(guān)配置注入,這樣就在代理中為我們把相關(guān)的事務(wù)處理掉了(開(kāi)啟正常提交事務(wù),異?;貪L事務(wù))。
- 真正的數(shù)據(jù)庫(kù)層的事務(wù)提交和回滾是通過(guò)binlog或者redo log實(shí)現(xiàn)的。
2. @Transactional基本配置解析
@Transactional
public void saveUser(){
User user = new User();
user.setAge(22);
user.setName("maskwang");
logger.info("save the user{}",user);
userRepository.save(user);
}
如上面這個(gè)例子一樣,很輕松的就能應(yīng)用事務(wù)。只需要在方法上加入@Transactional注解。當(dāng)@Transactional加在方法上,表示對(duì)該方法應(yīng)用事務(wù)。當(dāng)加在類上,表示對(duì)該類里面所有的方法都應(yīng)用相同配置的事務(wù)。接下來(lái)對(duì)@Transactional的參數(shù)解析。
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
-
transactionManager()表示應(yīng)用那個(gè)應(yīng)用那個(gè)TransactionManager.常用的有如下的事務(wù)管理器

-
isolation()表示隔離級(jí)別

臟讀:一事務(wù)對(duì)數(shù)據(jù)進(jìn)行了增刪改,但未提交,另一事務(wù)可以讀取到未提交的數(shù)據(jù)。如果第一個(gè)事務(wù)這時(shí)候回滾了,那么第二個(gè)事務(wù)就讀到了臟數(shù)據(jù)。
不可重復(fù)讀:一個(gè)事務(wù)中發(fā)生了兩次讀操作,第一次讀操作和第二次操作之間,另外一個(gè)事務(wù)對(duì)數(shù)據(jù)進(jìn)行了修改,這時(shí)候兩次讀取的數(shù)據(jù)是不一致的。
幻讀:第一個(gè)事務(wù)對(duì)一定范圍的數(shù)據(jù)進(jìn)行批量修改,第二個(gè)事務(wù)在這個(gè)范圍增加一條數(shù)據(jù),這時(shí)候第一個(gè)事務(wù)就會(huì)丟失對(duì)新增數(shù)據(jù)的修改。
不可重復(fù)讀的重點(diǎn)是修改 :同樣的條件, 你讀取過(guò)的數(shù)據(jù),再次讀取出來(lái)發(fā)現(xiàn)值不一樣了幻讀的重點(diǎn)在于新增或者刪除:同樣的條件, 第 1 次和第 2 次讀出來(lái)的記錄數(shù)不一樣
-
propagation()表示事務(wù)的傳播屬性
事務(wù)的傳播是指事務(wù)的嵌套的時(shí)候,它們的事務(wù)屬性。常見(jiàn)的傳播屬性有如下幾個(gè)
PROPAGATION_REQUIRED(spring 默認(rèn))
假設(shè)外層事務(wù) Service A 的 Method A() 調(diào)用 內(nèi)層Service B 的 Method B()。如果ServiceB.methodB() 的事務(wù)級(jí)別定義為 PROPAGATION_REQUIRED,那么執(zhí)行 ServiceA.methodA() 的時(shí)候spring已經(jīng)起了事務(wù),這時(shí)調(diào)用 ServiceB.methodB(),ServiceB.methodB() 看到自己已經(jīng)運(yùn)行在 ServiceA.methodA() 的事務(wù)內(nèi)部,就不再起新的事務(wù)。假如 ServiceB.methodB() 運(yùn)行的時(shí)候發(fā)現(xiàn)自己沒(méi)有在事務(wù)中,他就會(huì)為自己分配一個(gè)事務(wù)。不管如何,ServiceB.methodB()都會(huì)在事務(wù)中。PROPAGATION_REQUIRES_NEW
比如我們?cè)O(shè)計(jì) ServiceA.methodA() 的事務(wù)級(jí)別為 PROPAGATION_REQUIRED,ServiceB.methodB() 的事務(wù)級(jí)別為 PROPAGATION_REQUIRES_NEW。那么當(dāng)執(zhí)行到 ServiceB.methodB() 的時(shí)候,ServiceA.methodA() 所在的事務(wù)就會(huì)掛起,ServiceB.methodB() 會(huì)起一個(gè)新的事務(wù),等待 ServiceB.methodB() 的事務(wù)完成以后,它才繼續(xù)執(zhí)行。它與1中的區(qū)別在于ServiceB.methodB() 新起了一個(gè)事務(wù)。如過(guò)ServiceA.methodA() 發(fā)生異常,ServiceB.methodB() 已經(jīng)提交的事務(wù)是不會(huì)回滾的。PROPAGATION_SUPPORTS
假設(shè)ServiceB.methodB() 的事務(wù)級(jí)別為 PROPAGATION_SUPPORTS,那么當(dāng)執(zhí)行到ServiceB.methodB()時(shí),如果發(fā)現(xiàn)ServiceA.methodA()已經(jīng)開(kāi)啟了一個(gè)事務(wù),則加入當(dāng)前的事務(wù),如果發(fā)現(xiàn)ServiceA.methodA()沒(méi)有開(kāi)啟事務(wù),則自己也不開(kāi)啟事務(wù)。這種時(shí)候,內(nèi)部方法的事務(wù)性完全依賴于最外層的事務(wù)。
剩下幾種就不多介紹,可以參考這篇文章。http://www.itdecent.cn/p/249f2cd42692
readOnly()事務(wù)超時(shí)設(shè)置.超過(guò)這個(gè)時(shí)間,發(fā)生回滾readOnly()只讀事務(wù)
從這一點(diǎn)設(shè)置的時(shí)間點(diǎn)開(kāi)始(時(shí)間點(diǎn)a)到這個(gè)事務(wù)結(jié)束的過(guò)程中,其他事務(wù)所提交的數(shù)據(jù),該事務(wù)將看不見(jiàn)?。ú樵冎胁粫?huì)出現(xiàn)別人在時(shí)間點(diǎn)a之后提交的數(shù)據(jù))。
注意是一次執(zhí)行多次查詢來(lái)統(tǒng)計(jì)某些信息,這時(shí)為了保證數(shù)據(jù)整體的一致性,要用只讀事務(wù)rollbackFor()導(dǎo)致事務(wù)回滾的異常類數(shù)組.rollbackForClassName()導(dǎo)致事務(wù)回滾的異常類名字?jǐn)?shù)組noRollbackFor不會(huì)導(dǎo)致事務(wù)回滾的異常類數(shù)組noRollbackForClassName不會(huì)導(dǎo)致事務(wù)回滾的異常類名字?jǐn)?shù)組
3. @Transactional 使用應(yīng)該注意的地方
3.1 默認(rèn)情況下,如果在事務(wù)中拋出了未檢查異常(繼承自 RuntimeException 的異常)或者 Error,則 Spring 將回滾事務(wù);除此之外,Spring 不會(huì)回滾事務(wù)。你如果想要在特定的異?;貪L可以考慮rollbackFor()等屬性
3.2 @Transactional 只能應(yīng)用到 public 方法才有效。
這是因?yàn)樵谑褂?Spring AOP 代理時(shí),Spring 會(huì)調(diào)用 TransactionInterceptor在目標(biāo)方法執(zhí)行前后進(jìn)行攔截之前,DynamicAdvisedInterceptor(CglibAopProxy的內(nèi)部類)的的 intercept方法或 JdkDynamicAopProxy 的 invoke 方法會(huì)間接調(diào)用 AbstractFallbackTransactionAttributeSource(Spring 通過(guò)這個(gè)類獲取@Transactional 注解的事務(wù)屬性配置屬性信息)的 computeTransactionAttribute 方法。
@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
//這里判斷是否是public方法
if(this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
//省略其他代碼
若不是 public,就不會(huì)獲取@Transactional 的屬性配置信息,最終會(huì)造成不會(huì)用 TransactionInterceptor 來(lái)攔截該目標(biāo)方法進(jìn)行事務(wù)管理。整個(gè)事務(wù)執(zhí)行的時(shí)序圖如下。

3.3 Spring 的 AOP 的自調(diào)用問(wèn)題
在 Spring 的 AOP 代理下,只有目標(biāo)方法由外部調(diào)用,目標(biāo)方法才由 Spring 生成的代理對(duì)象來(lái)管理,這會(huì)造成自調(diào)用問(wèn)題。若同一類中的其他沒(méi)有@Transactional注解的方法內(nèi)部調(diào)用有@Transactional注解的方法,有@Transactional注解的方法的事務(wù)被忽略,不會(huì)發(fā)生回滾。這個(gè)問(wèn)題是由于Spring AOP 代理造成的(如下面代碼所示)。之所以沒(méi)有應(yīng)用事務(wù),是因?yàn)樵趦?nèi)部調(diào)用,而代理后的類(把目標(biāo)類作為成員變量靜態(tài)代理)只是調(diào)用成員變量中的對(duì)應(yīng)方法,自然也就沒(méi)有aop中的advice,造成只能調(diào)用父類的方法。另外一個(gè)問(wèn)題是只能應(yīng)用在public方法上。為解決這兩個(gè)問(wèn)題,使用 AspectJ 取代 Spring AOP 代理。
@Transactional
public void saveUser(){
User user = new User();
user.setAge(22);
user.setName("mask");
logger.info("save the user{}",user);
userRepository.save(user);
// throw new RuntimeException("exception");
}
public void saveUserBack(){
saveUser(); //自調(diào)用發(fā)生
}
3.4 自注入解決辦法
@Service
public class UserService {
Logger logger = LoggerFactory.getLogger(UserService.class);
@Autowired
UserRepository userRepository;
@Autowired
UserService userService; //自注入來(lái)解決
@Transactional
public void saveUser(){
User user = new User();
user.setAge(22);
user.setName("mask");
logger.info("save the user{}",user);
userRepository.save(user);
// throw new RuntimeException("exception");
}
public void saveUserBack(){
saveUser();
}
}
另外也可以把注解加到類上來(lái)解決。
4. 總結(jié)
@Transactional用起來(lái)是方便,但是我們需要明白它背后的原理,避免入坑。另外@Transactional不建議用在處理時(shí)間過(guò)長(zhǎng)的事務(wù)。因?yàn)?,它?huì)一直持有數(shù)據(jù)庫(kù)線程池的連接,造成不能及時(shí)返回。就是盡量是的事務(wù)的處理時(shí)間短。
參考文章:
Spring @Transactional的使用及原理
深入理解 Spring 事務(wù)原理
透徹的掌握 Spring 中@transactional 的使用
在同一個(gè)類中,一個(gè)方法調(diào)用另外一個(gè)有注解(比如@Async,@Transational)的方法,注解失效的原因和解決方法