javascript
Spring Boot 中使用 @Transactional 注解配置事务管理
From: https://blog.csdn.net/nextyu/article/details/78669997
事務(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ù)管理。
需要明確幾點(diǎn):
??? 默認(rèn)配置下 Spring 只會(huì)回滾運(yùn)行時(shí)、未檢查異常(繼承自 RuntimeException 的異常)或者 Error。參考這里
??? @Transactional 注解只能應(yīng)用到 public 方法才有效。參考這里 Method visibility and @Transactional
以下的示例使用的是 mybatis,所以 spring boot 會(huì)自動(dòng)配置一個(gè) DataSourceTransactionManager,我們只需在方法(或者類(lèi))加上 @Transactional 注解,就自動(dòng)納入 Spring 的事務(wù)管理了。
簡(jiǎn)單的使用方法
只需在方法加上 @Transactional 注解就可以了。
如下有一個(gè)保存用戶(hù)的方法,加入 @Transactional 注解,使用默認(rèn)配置,拋出異常之后,事務(wù)會(huì)自動(dòng)回滾,數(shù)據(jù)不會(huì)插入到數(shù)據(jù)庫(kù)。
@Transactional
@Override
public void save() {
??? User user = new User("服部半藏");
??? userMapper.insertSelective(user);
??? if (true) {
??????? throw new RuntimeException("save 拋異常了");
??? }
}
我們可以從日志里面看出這些信息
TIM截圖20171129135813
@Transactional 注解的屬性介紹
下面分別介紹一下 @Transactional 的幾個(gè)屬性。
value 和 transactionManager 屬性
它們兩個(gè)是一樣的意思。當(dāng)配置了多個(gè)事務(wù)管理器時(shí),可以使用該屬性指定選擇哪個(gè)事務(wù)管理器。
propagation 屬性
事務(wù)的傳播行為,默認(rèn)值為 Propagation.REQUIRED。
可選的值有:
??? Propagation.REQUIRED
??? 如果當(dāng)前存在事務(wù),則加入該事務(wù),如果當(dāng)前不存在事務(wù),則創(chuàng)建一個(gè)新的事務(wù)。
??? Propagation.SUPPORTS
??? 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前不存在事務(wù),則以非事務(wù)的方式繼續(xù)運(yùn)行。
??? Propagation.MANDATORY
??? 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前不存在事務(wù),則拋出異常。
??? Propagation.REQUIRES_NEW
??? 重新創(chuàng)建一個(gè)新的事務(wù),如果當(dāng)前存在事務(wù),暫停當(dāng)前的事務(wù)。
??? Propagation.NOT_SUPPORTED
??? 以非事務(wù)的方式運(yùn)行,如果當(dāng)前存在事務(wù),暫停當(dāng)前的事務(wù)。
??? Propagation.NEVER
??? 以非事務(wù)的方式運(yùn)行,如果當(dāng)前存在事務(wù),則拋出異常。
??? Propagation.NESTED
??? 和 Propagation.REQUIRED 效果一樣。
這些概念理解起來(lái)實(shí)在是有點(diǎn)兒抽象,后文會(huì)用代碼示例解釋說(shuō)明。
isolation 屬性
事務(wù)的隔離級(jí)別,默認(rèn)值為 Isolation.DEFAULT。
可選的值有:
??? Isolation.DEFAULT
??? 使用底層數(shù)據(jù)庫(kù)默認(rèn)的隔離級(jí)別。
??? Isolation.READ_UNCOMMITTED
??? Isolation.READ_COMMITTED
??? Isolation.REPEATABLE_READ
??? Isolation.SERIALIZABLE
timeout 屬性
事務(wù)的超時(shí)時(shí)間,默認(rèn)值為-1。如果超過(guò)該時(shí)間限制但事務(wù)還沒(méi)有完成,則自動(dòng)回滾事務(wù)。
readOnly 屬性
指定事務(wù)是否為只讀事務(wù),默認(rèn)值為 false;為了忽略那些不需要事務(wù)的方法,比如讀取數(shù)據(jù),可以設(shè)置 read-only 為 true。
rollbackFor 屬性
用于指定能夠觸發(fā)事務(wù)回滾的異常類(lèi)型,可以指定多個(gè)異常類(lèi)型。
noRollbackFor 屬性
拋出指定的異常類(lèi)型,不回滾事務(wù),也可以指定多個(gè)異常類(lèi)型。
@Transactional 的 propagation 屬性代碼示例
比如如下代碼,save 方法首先調(diào)用了 method1 方法,然后拋出了異常,就會(huì)導(dǎo)致事務(wù)回滾,如下兩條數(shù)據(jù)都不會(huì)插入數(shù)據(jù)庫(kù)。
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {
??? method1();
??? User user = new User("服部半藏");
??? userMapper.insertSelective(user);
??? if (true) {
??????? throw new RuntimeException("save 拋異常了");
??? }
}
public void method1() {
??? User user = new User("宮本武藏");
??? userMapper.insertSelective(user);
}
現(xiàn)在有需求如下,就算 save 方法的后面拋異常了,也不能影響 method1 方法的數(shù)據(jù)插入。或許很多人的想法如下,給 method1 頁(yè)加入一個(gè)新的事務(wù),這樣 method1 就會(huì)在這個(gè)新的事務(wù)中執(zhí)行,原來(lái)的事務(wù)不會(huì)影響到新的事務(wù)。比如 method1 方法上面再加入注解 @Transactional,設(shè)置 propagation 屬性為 Propagation.REQUIRES_NEW,代碼如下。
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {
??? method1();
??? User user = new User("服部半藏");
??? userMapper.insertSelective(user);
??? if (true) {
??????? throw new RuntimeException("save 拋異常了");
??? }
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
??? User user = new User("宮本武藏");
??? userMapper.insertSelective(user);
}
?
運(yùn)行之后,發(fā)現(xiàn)然并卵,數(shù)據(jù)也是沒(méi)有插入數(shù)據(jù)庫(kù)。怎么肥四,看起來(lái)很不科學(xué)。我們先來(lái)看看日志內(nèi)容。
TIM截圖20171129150737
從日志內(nèi)容可以看出,其實(shí)兩個(gè)方法都是處于同一個(gè)事務(wù)中,method1 方法并沒(méi)有創(chuàng)建一個(gè)新的事務(wù)。
這就得看看 Spring 官方文檔了。
??? In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.
大概意思:在默認(rèn)的代理模式下,只有目標(biāo)方法由外部調(diào)用,才能被 Spring 的事務(wù)攔截器攔截。在同一個(gè)類(lèi)中的兩個(gè)方法直接調(diào)用,是不會(huì)被 Spring 的事務(wù)攔截器攔截,就像上面的 save 方法直接調(diào)用了同一個(gè)類(lèi)中的 method1方法,method1 方法不會(huì)被 Spring 的事務(wù)攔截器攔截。可以使用 AspectJ 取代 Spring AOP 代理來(lái)解決這個(gè)問(wèn)題,但是這里暫不討論。
為了解決這個(gè)問(wèn)題,我們可以新建一個(gè)類(lèi)。
@Service
public class OtherServiceImpl implements OtherService {
??? @Autowired
??? private UserMapper userMapper;
??? @Transactional(propagation = Propagation.REQUIRES_NEW)
??? @Override
??? public void method1() {
??????? User user = new User("風(fēng)魔小太郎");
??????? userMapper.insertSelective(user);
??? }
}
?
然后在 save 方法中調(diào)用 otherService.method1 方法
@Autowired
private OtherService otherService;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {
??? otherService.method1();
??? User user = new User("服部半藏");
??? userMapper.insertSelective(user);
??? if (true) {
??????? throw new RuntimeException("save 拋異常了");
??? }
}
??
這下,otherService.method1 方法的數(shù)據(jù)插入成功,save 方法的數(shù)據(jù)未插入,事務(wù)回滾。
繼續(xù)看一下日志內(nèi)容
TIM截圖20171129153731
從日志可以看出,首先創(chuàng)建了 save 方法的事務(wù),由于 otherService.method1 方法的 @Transactional 的 propagation 屬性為 Propagation.REQUIRES_NEW ,所以接著暫停了 save 方法的事務(wù),重新創(chuàng)建了 otherService.method1 方法的事務(wù),接著 otherService.method1 方法的事務(wù)提交,接著 save 方法的事務(wù)回滾。這就印證了只有目標(biāo)方法由外部調(diào)用,才能被 Spring 的事務(wù)攔截器攔截。
還有幾個(gè)示例如下。
接著把 save 方法的 @Transactional 注解去掉,otherService.method1 的 @Transactional 注解保持不變,從日志就可以看出,只會(huì)創(chuàng)建一個(gè) otherService.method1 方法的事務(wù),兩條數(shù)據(jù)都會(huì)插入。
@Autowired
private OtherService otherService;
//??? @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {
??? otherService.method1();
??? User user = new User("服部半藏");
??? userMapper.insertSelective(user);
??? if (true) {
??????? throw new RuntimeException("save 拋異常了");
??? }
}
?
接著把 save 方法的 @Transactional 注解去掉,save 方法改為調(diào)用內(nèi)部的 method1 方法,從日志就可以看出,完全沒(méi)有創(chuàng)建任何事務(wù),兩條數(shù)據(jù)都會(huì)插入。
//??? @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {
??? method1();
??? User user = new User("服部半藏");
??? userMapper.insertSelective(user);
??? if (true) {
??????? throw new RuntimeException("save 拋異常了");
??? }
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
??? User user = new User("宮本武藏");
??? userMapper.insertSelective(user);
}
?
這樣,其他的幾個(gè) propagation 屬性值也就比較好理解了。
@Transactional 事務(wù)實(shí)現(xiàn)機(jī)制
在應(yīng)用系統(tǒng)調(diào)用聲明了 @Transactional 的目標(biāo)方法時(shí),Spring Framework 默認(rèn)使用 AOP 代理,在代碼運(yùn)行時(shí)生成一個(gè)代理對(duì)象,根據(jù) @Transactional 的屬性配置信息,這個(gè)代理對(duì)象決定該聲明 @Transactional 的目標(biāo)方法是否由攔截器 TransactionInterceptor 來(lái)使用攔截,在 TransactionInterceptor 攔截時(shí),會(huì)在目標(biāo)方法開(kāi)始執(zhí)行之前創(chuàng)建并加入事務(wù),并執(zhí)行目標(biāo)方法的邏輯, 最后根據(jù)執(zhí)行情況是否出現(xiàn)異常,利用抽象事務(wù)管理器 AbstractPlatformTransactionManager 操作數(shù)據(jù)源 DataSource 提交或回滾事務(wù)。
Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 兩種,以 CglibAopProxy 為例,對(duì)于 CglibAopProxy,需要調(diào)用其內(nèi)部類(lèi)的 DynamicAdvisedInterceptor 的 intercept 方法。對(duì)于 JdkDynamicAopProxy,需要調(diào)用其 invoke 方法。
Spring-transaction-mechanis
正如上文提到的,事務(wù)管理的框架是由抽象事務(wù)管理器 AbstractPlatformTransactionManager 來(lái)提供的,而具體的底層事務(wù)處理實(shí)現(xiàn),由 PlatformTransactionManager 的具體實(shí)現(xiàn)類(lèi)來(lái)實(shí)現(xiàn),如事務(wù)管理器 DataSourceTransactionManager。不同的事務(wù)管理器管理不同的數(shù)據(jù)資源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。
Spring-TransactionManager-hierarchy-subtypes
源碼地址
??? https://github.com/nextyu/spring-transaction-demo
參考資料
??? Spring 官方文檔
??? Spring boot 官方文檔
??? Mybatis
??? 資料
??? 資料
??? 資料
結(jié)語(yǔ)
由于本人知識(shí)和能力有限,文中如有沒(méi)說(shuō)清楚的地方,希望大家能在評(píng)論區(qū)指出,以幫助我將博文寫(xiě)得更好。
?
總結(jié)
以上是生活随笔為你收集整理的Spring Boot 中使用 @Transactional 注解配置事务管理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: EIGRP个人学习笔记
- 下一篇: 学习 Spring Boot:(二十九)