Spring事務(wù)為什么會(huì)失效?
不用Spring管理事務(wù)?
讓我們先來看一下不用spring管理事務(wù)時(shí),各種框架是如何管理事務(wù)的。
使用JDBC來管理事務(wù):

使用Hibernate來管理事務(wù):

業(yè)務(wù)邏輯和事務(wù)代碼是耦合到一塊的,并且和框架的具體api綁定了。當(dāng)我們換一種框架來實(shí)現(xiàn)時(shí),里面對事務(wù)控制的代碼就要推倒重寫,并不一定能保證替換后的api和之前的api有相同的行為。
「統(tǒng)一的事務(wù)抽象」
基于這些問題,Spring抽象了一些事務(wù)相關(guān)的頂層接口。無論是全局事務(wù)還是本地事務(wù),JTA,JDBC還是Hibernate,Spring都使用統(tǒng)一的編程模型。使得應(yīng)用程序可以很容易的在全局事務(wù)與本地事務(wù),或者不同事物框架之間進(jìn)行切換。
「下圖為Spring事物抽象的核心類」

常用api  | 接口  | 
PlatformTransactionManager  | 對事務(wù)進(jìn)行管理  | 
TransactionDefinition  | 定義事務(wù)的相關(guān)屬性,例如隔離級別,傳播行為  | 
TransactionStatus  | 保存事務(wù)狀態(tài)  | 
針對不同的數(shù)據(jù)訪問技術(shù),使用不用的PlatformTransactionManager類即可。
數(shù)據(jù)訪問技術(shù)  | PlatformTransactionManager實(shí)現(xiàn)類  | 
JDBC/Mybatis  | DataSourceTransactionManager  | 
Hibernate  | HibernateTransactionManager  | 
Jpa  | JpaTransactionManager  | 
Jms  | JmsTransactionManager  | 
編程式事務(wù)管理
當(dāng)我們使用Spring的事務(wù)時(shí),可以使用編程式事務(wù)或者聲明式事務(wù)。
當(dāng)使用編程式事務(wù)的時(shí)候,可以直接使用事務(wù)的頂層接口,也可以使用模版類TransactionTemplate。
使用PlatformTransactionManager



使用TransactionTemplate
當(dāng)我們直接使用PlatformTransactionManager來管理事務(wù)時(shí),有很多模版代碼。例如業(yè)務(wù)代碼正常執(zhí)行,提交事務(wù),否則回滾事務(wù)。我們可以把這部分模版代碼封裝成一個(gè)模版類,這樣使用起來就很方便了,如下所示:

如下圖所示,TransactionTemplate#execute方法就是一個(gè)典型的模版方法:

我們可以傳入如下2個(gè)接口的實(shí)現(xiàn)類來執(zhí)行業(yè)務(wù)邏輯,TransactionCallback(需要返回執(zhí)行結(jié)果)或TransactionCallbackWithoutResult(不需要返回結(jié)果)。
聲明式事務(wù)管理
為了讓使用更加簡潔,Spring直接把事務(wù)代碼的執(zhí)行放到切面中了,我們只需要在業(yè)務(wù)代碼方法上加上一個(gè)@Transactional注解即可,這種方式我們最常用哈。
使用@Transactional注解
此時(shí)事務(wù)相關(guān)的定義我們就可以通過@Transactional注解來設(shè)置了。
屬性名  | 類型  | 描述  | 默認(rèn)值  | 
value(和transactionManager互為別名)  | String  | 當(dāng)在配置文件中有多個(gè)PlatformTransactionManager ,用該屬性指定選擇哪個(gè)事務(wù)管理器  | 空字符串""  | 
propagation  | 枚舉:Propagation  | 事務(wù)的傳播行為  | REQUIRED  | 
isolation  | 枚舉:Isolation  | 事務(wù)的隔離度  | DEFAULT  | 
timeout  | int  | 事務(wù)的超時(shí)時(shí)間。如果超過該時(shí)間限制但事務(wù)還沒有完成,則自動(dòng)回滾事務(wù)  | -1  | 
readOnly  | boolean  | 指定事務(wù)是否為只讀事務(wù)  | false  | 
rollbackFor  | Class[]  | 需要回滾的異常  | 空數(shù)組{}  | 
rollbackForClassName  | String[]  | 需要回滾的異常類名  | 空數(shù)組{}  | 
noRollbackFor  | Class[]  | 不需要回滾的異常  | 空數(shù)組{}  | 
noRollbackForClassName  | String[]  | 不需要回滾的異常類名  | 空數(shù)組{}  | 

源碼解析
我們需要在配置類上加上@EnableTransactionManagement注解,來開啟spring事務(wù)管理功能。

「TransactionManagementConfigurationSelector#selectImports」

往容器中注入AutoProxyRegistrar和ProxyTransactionManagementConfiguration這2個(gè)類,那這2個(gè)類有啥作用呢?(源碼太多了,我就不貼代碼一步一步分析了,主要是理清思路)。

AutoProxyRegistrar主要就是往容器中注入一個(gè)類InfrastructureAdvisorAutoProxyCreator,這個(gè)類有什么作用呢?
「看一下繼承關(guān)系,原來是繼承自AbstractAutoProxyCreator,用來實(shí)現(xiàn)自動(dòng)代理沒跑了!」

BeanFactoryTransactionAttributeSourceAdvisor主要就是往容器中注入了一個(gè)Advisor類,用來保存Pointcut和Advice。

對應(yīng)的Pointcut為TransactionAttributeSourcePointcut的實(shí)現(xiàn)類,是一個(gè)匿名內(nèi)部類,即篩選的邏輯是通過TransactionAttributeSourcePointcut類來實(shí)現(xiàn)的。
BeanFactoryTransactionAttributeSourceAdvisor

對應(yīng)的Advice的實(shí)現(xiàn)類為TransactionInterceptor,即針對事務(wù)增強(qiáng)的邏輯都在這個(gè)類中。
篩選的邏輯我們就先不分析了,后面會(huì)再簡單提一下。
我們來看針對事務(wù)增強(qiáng)的邏輯,當(dāng)執(zhí)行被@Transactional標(biāo)記的方法時(shí),會(huì)調(diào)用到如下方法(TransactionInterceptor#invoke有點(diǎn)類似我們的@Around)。
TransactionInterceptor#invoke

TransactionAspectSupport#invokeWithinTransaction

我挑出這個(gè)方法比較重要的幾個(gè)部分來分析吧(上圖圈出來的部分)。
- 如果需要的話開啟事務(wù)(和傳播屬性相關(guān),我們后面會(huì)提到)。
 - 執(zhí)行業(yè)務(wù)邏輯。
 - 如果發(fā)生異常則會(huì)滾事務(wù)。
 - 如果正常執(zhí)行則提交事務(wù)。
 
「所以當(dāng)發(fā)生異常需要會(huì)滾的時(shí)候,我們一定不要自己把異常try catch掉,不然事務(wù)會(huì)正常提交」。
TransactionAspectSupport#createTransactionIfNecessary

當(dāng)開啟事務(wù)的時(shí)候,可以看到各種傳播屬性的行為(即@Transactional方法調(diào)用@Transactional方法會(huì)發(fā)生什么?)
AbstractPlatformTransactionManager#getTransaction

Spring事務(wù)的傳播行為在Propagation枚舉類中定義了如下幾種選擇。
「支持當(dāng)前事務(wù)」
- REQUIRED :如果當(dāng)前存在事務(wù),則加入該事務(wù)。如果當(dāng)前沒有事務(wù),則創(chuàng)建一個(gè)新的事務(wù)。
 - SUPPORTS:如果當(dāng)前存在事務(wù),則加入該事務(wù) 。如果當(dāng)前沒有事務(wù), 則以非事務(wù)的方式繼續(xù)運(yùn)行。
 - MANDATORY :如果當(dāng)前存在事務(wù),則加入該事務(wù) 。如果當(dāng)前沒有事務(wù),則拋出異常。
 
「不支持當(dāng)前事務(wù)」
- REQUIRES_NEW :如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起,創(chuàng)建一個(gè)新事務(wù)。
 - NOT_SUPPORTED :如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起,以非事務(wù)方式運(yùn)行,。
 - NEVER :如果當(dāng)前存在事務(wù),則拋出異常。
 
「其他情況」
NESTED :如果當(dāng)前存在事務(wù),則創(chuàng)建一個(gè)事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)來執(zhí)行 。如果當(dāng)前沒有事務(wù),則該取值等價(jià)于REQUIRED。
以NESTED啟動(dòng)的事務(wù)內(nèi)嵌于外部事務(wù)中 (如果存在外部事務(wù)的話),此時(shí)內(nèi)嵌事務(wù)并不是一個(gè)獨(dú)立的事務(wù),它依賴于外部事務(wù)。只有通過外部事務(wù)的提交,才能引起內(nèi)部事務(wù)的提交,嵌套的子事務(wù)不能單獨(dú)提交。
事務(wù)失效的場景有哪些?
因?yàn)槲覀兘?jīng)常使用聲明式事務(wù),如果一步消息就會(huì)導(dǎo)致事務(wù)失效,所以我們就從源碼角度來盤一下事務(wù)為什么失效。
異常被你try catch了
首先就是我們上面剛提到的,「異常被你try catch了」。因?yàn)槁暶魇绞挛锸峭ㄟ^目標(biāo)方法是否拋出異常來決定是提交事物還是會(huì)滾事物的。
自調(diào)用
當(dāng)自調(diào)用時(shí),方法執(zhí)行不會(huì)經(jīng)過代理對象,所以會(huì)導(dǎo)致事務(wù)失效。
// 事務(wù)失效
@Service
public class UserServiceV2Impl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addUser(String name, String location) {
doAdd(name);
}
@Transactional
public void doAdd(String name) {
String sql = "insert into user (`name`) values (?)";
jdbcTemplate.update(sql, new Object[]{name});
throw new RuntimeException("保存用戶失敗");
}
}
我們可以通過如下三種方式來解決自調(diào)用失效的場景。
「1.@Autowired注入代理對象,然后調(diào)用方法」
// @Service
public class UserServiceV3Impl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private UserService userService;
@Override
public void addUser(String name, String location) {
userService.doAdd(name);
}
@Override
@Transactional
public void doAdd(String name) {
String sql = "insert into user (`name`) values (?)";
jdbcTemplate.update(sql, new Object[]{name});
throw new RuntimeException("保存用戶失敗");
}
}
「2.從ApplicationContext獲取代理對象,然后調(diào)用方法」
@Service
public class UserServiceV4Impl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ApplicationContext applicationContext;
@Override
public void addUser(String name, String location) {
UserService userService = applicationContext.getBean(UserService.class);
userService.doAdd(name);
}
@Override
@Transactional
public void doAdd(String name) {
String sql = "insert into user (`name`) values (?)";
jdbcTemplate.update(sql, new Object[]{name});
throw new RuntimeException("保存用戶失敗");
}
}
「3.進(jìn)行如下設(shè)置@EnableAspectJAutoProxy(exposeProxy = true),從AopContext中獲取代理對象,然后調(diào)用方法」
@Service
public class UserServiceV5Impl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addUser(String name, String location) {
UserService userService = (UserService) AopContext.currentProxy();
userService.doAdd(name);
}
@Override
@Transactional
public void doAdd(String name) {
String sql = "insert into user (`name`) values (?)";
jdbcTemplate.update(sql, new Object[]{name});
throw new RuntimeException("保存用戶失敗");
}
}
非public方法導(dǎo)致事務(wù)失效
我們先來猜一下為什么非public方法會(huì)導(dǎo)致事務(wù)失效?
「難道是因?yàn)榉莗ublic方法不會(huì)生成代理對象?」
我們給一個(gè)非public方法加上@Transactional,debug到如下代碼看一下是否會(huì)生成代理對象。
AbstractAutoProxyCreator#wrapIfNecessary

「結(jié)論是不會(huì)生成代理對象,那為什么不會(huì)生成代理對象呢?」
應(yīng)該就是不符合Pointcut的要求了唄,我們在前面已經(jīng)提到了事務(wù)對應(yīng)的Pointcut為TransactionAttributeSourcePointcut。
TransactionAttributeSourcePointcut#matches

matches方法返回false,為什么會(huì)返回false呢?
一直debug發(fā)現(xiàn)是如下代碼導(dǎo)致的。
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

即public方法能正常生成代理對象,而非public方法因?yàn)椴环螾ointcut的要求,根本就不會(huì)生成代理對象。
異常類型不正確,默認(rèn)只支持RuntimeException和Error,不支持檢查異常

「為什么不支持檢查異常呢?」
拿出我們上面分析過的代碼:

當(dāng)執(zhí)行業(yè)務(wù)邏輯發(fā)生異常的時(shí)候,會(huì)調(diào)用到TransactionAspectSupport#completeTransactionAfterThrowing方法。

可以看到對異常類型做了判斷,根據(jù)返回的結(jié)果來決定是否會(huì)滾事務(wù),會(huì)調(diào)用到如下方法進(jìn)行判斷。
RuleBasedTransactionAttribute#rollbackOn

如果用戶指定了回滾的異常類型,則根據(jù)用戶指定的規(guī)則來判斷,否則用默認(rèn)的規(guī)則。
DefaultTransactionAttribute

默認(rèn)的規(guī)則為只支持RuntimeException和Error。
我們可以通過@Transactional屬性指定回滾的類型,一般為Exception即可。
@Transactional(rollbackFor = Exception.class)
















 
 
 











 
 
 
 