Spring事務(wù)為什么會(huì)失效?
不用Spring管理事務(wù)?
讓我們先來看一下不用spring管理事務(wù)時(shí),各種框架是如何管理事務(wù)的。
使用JDBC來管理事務(wù):
使用Hibernate來管理事務(wù):
業(yè)務(wù)邏輯和事務(wù)代碼是耦合到一塊的,并且和框架的具體api綁定了。當(dāng)我們換一種框架來實(shí)現(xiàn)時(shí),里面對(duì)事務(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 | 對(duì)事務(wù)進(jìn)行管理 |
TransactionDefinition | 定義事務(wù)的相關(guān)屬性,例如隔離級(jí)別,傳播行為 |
TransactionStatus | 保存事務(wù)狀態(tài) |
針對(duì)不同的數(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。
對(duì)應(yīng)的Pointcut為TransactionAttributeSourcePointcut的實(shí)現(xiàn)類,是一個(gè)匿名內(nèi)部類,即篩選的邏輯是通過TransactionAttributeSourcePointcut類來實(shí)現(xiàn)的。
BeanFactoryTransactionAttributeSourceAdvisor
對(duì)應(yīng)的Advice的實(shí)現(xiàn)類為TransactionInterceptor,即針對(duì)事務(wù)增強(qiáng)的邏輯都在這個(gè)類中。
篩選的邏輯我們就先不分析了,后面會(huì)再簡單提一下。
我們來看針對(duì)事務(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)過代理對(duì)象,所以會(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注入代理對(duì)象,然后調(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獲取代理對(duì)象,然后調(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中獲取代理對(duì)象,然后調(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ì)生成代理對(duì)象?」
我們給一個(gè)非public方法加上@Transactional,debug到如下代碼看一下是否會(huì)生成代理對(duì)象。
AbstractAutoProxyCreator#wrapIfNecessary
「結(jié)論是不會(huì)生成代理對(duì)象,那為什么不會(huì)生成代理對(duì)象呢?」
應(yīng)該就是不符合Pointcut的要求了唄,我們?cè)谇懊嬉呀?jīng)提到了事務(wù)對(duì)應(yīng)的Pointcut為TransactionAttributeSourcePointcut。
TransactionAttributeSourcePointcut#matches
matches方法返回false,為什么會(huì)返回false呢?
一直debug發(fā)現(xiàn)是如下代碼導(dǎo)致的。
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
即public方法能正常生成代理對(duì)象,而非public方法因?yàn)椴环螾ointcut的要求,根本就不會(huì)生成代理對(duì)象。
異常類型不正確,默認(rèn)只支持RuntimeException和Error,不支持檢查異常
「為什么不支持檢查異常呢?」
拿出我們上面分析過的代碼:
當(dāng)執(zhí)行業(yè)務(wù)邏輯發(fā)生異常的時(shí)候,會(huì)調(diào)用到TransactionAspectSupport#completeTransactionAfterThrowing方法。
可以看到對(duì)異常類型做了判斷,根據(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)