Spring事務(wù)失效的各種場(chǎng)景(13種)
一、訪問(wèn)權(quán)限
Java的訪問(wèn)權(quán)限主要是:private、default、protected、public,它們的權(quán)限則是依次變大。
如果我們?cè)陂_發(fā)的時(shí)候定義錯(cuò)誤的訪問(wèn)權(quán)限,就會(huì)導(dǎo)致事務(wù)出現(xiàn)問(wèn)題
@Service
public class DemoService {
@Transactional
private void query(Demo demo) {
}
}

我們可以查看源碼,可以明白,spring事務(wù)的實(shí)現(xiàn)AbstractFallbackTransactionAttributeSource類的computeTransactionAttribute方法中有個(gè)判斷,如果目標(biāo)方法不是public,則TransactionAttribute返回null,即不支持事務(wù)。
二、方法用final修飾
我們?cè)趯W(xué)習(xí)的時(shí)候都知道,某個(gè)方法不想被子類繼承的時(shí)候就會(huì)加上關(guān)鍵字final,這種寫法正常來(lái)說(shuō)是沒(méi)有問(wèn)題的,但是如果該方法被用上事務(wù)就不行了
@Service
public class DemoService {
@Transactional
public final void query(Demo demo) {
}
}
這樣的話會(huì)導(dǎo)致事務(wù)的失效,因?yàn)閟pring事務(wù)底層實(shí)現(xiàn)使用了代理,aop,通過(guò)jdk的動(dòng)態(tài)代理或者cglib,生成了代理類,在代理類中實(shí)現(xiàn)了事務(wù)功能,如果方法被final修飾,無(wú)法重寫該方法,也就無(wú)法添加事務(wù)的功能了

像idea這里就提出了提示,同樣的如果方法是static方法也是不行的

因?yàn)殪o態(tài)方法是屬于類的,而不是屬于對(duì)象的,無(wú)法重寫靜態(tài)方法所以也就不可能實(shí)現(xiàn)事務(wù)
三、方法內(nèi)部調(diào)用
在同一個(gè)類的service中,調(diào)用其他的事務(wù)方法
@Service
public class DemoService {
@Transactional
public void query(Demo demo) {
save(demo);
}
@Transactional
public void save(Demo demo) {
}
}
可以看到,query方法調(diào)用了save的方法,由于spring的事務(wù)實(shí)現(xiàn)是因?yàn)閍op生成代理,這樣是直接調(diào)用了this對(duì)象,所以也不會(huì)生成事務(wù)。
解決方法:
1、增加一個(gè)service,把一個(gè)事務(wù)的方法移到新增加的service方法里面,然后進(jìn)行注入再調(diào)用
@Service
public class DemoTwoService {
@Transactional
public void save(Demo demo) {
}
}
@Service
public class DemoService {
@Autowired
DemoTwoService demoTwoService;
@Transactional
public void query(Demo demo) {
demoTwoService.save(demo);
}
}
2、在自己類中注入自己
@Service
public class DemoService {
@Autowired
DemoService demoService;
@Transactional
public void query(Demo demo) {
demoService.save(demo);
}
@Transactional
public void save(Demo demo) {
}
}
由于這種寫法基于spring的三級(jí)緩存不會(huì)導(dǎo)致,循環(huán)依賴的問(wèn)題出現(xiàn)
3、通過(guò)AopContentent
@Service
public class DemoService {
@Transactional
public void query(Demo demo) {
DemoService demoService = (DemoService)AopContext.currentProxy();
demoService.save(demo);
}
@Transactional
public void save(Demo demo) {
}
}
四、沒(méi)有被spring管理
在使用spring事務(wù)的時(shí)候,對(duì)象要被spring進(jìn)行管理,也就是需要?jiǎng)?chuàng)建bean,一般我們都會(huì)加@Controller、@Service、@Component、@Repository等注解,可以自動(dòng)實(shí)現(xiàn)bean實(shí)例化和依賴注入的功能。,如果忘記加了,也會(huì)導(dǎo)致,事務(wù)的失效
五、多線程調(diào)用
@Service
public class DemoService {
@Autowired
DemoTwoService demoTwoService;
@Transactional
public void query(Demo demo) {
new Thread(()->{
demoTwoService.save(demo);
}).start();
}
}
通過(guò)上面的例子,我們可以知道,query調(diào)用了事務(wù)方法save,但是事務(wù)方法在另一個(gè)線程里面調(diào)用,這樣會(huì)導(dǎo)致兩個(gè)方法在不同的一個(gè)線程中,獲取的數(shù)據(jù)庫(kù)連接也不一樣,所以會(huì)是兩個(gè)不同的事務(wù),如果save的方法拋出了異常,query回滾是不可能的,看過(guò)spring源碼,我們可以知道,spring的事務(wù)是通過(guò)連接數(shù)據(jù)庫(kù)來(lái)實(shí)現(xiàn)的,當(dāng)前線程保存了一個(gè)map,key—數(shù)據(jù)源,value----數(shù)據(jù)庫(kù)連接,事務(wù)其實(shí)就是指向同一個(gè)連接的,只有擁有同一個(gè)數(shù)據(jù)庫(kù)連接才能同時(shí)提交和回滾,如果在不同的線程,數(shù)據(jù)庫(kù)的連接不是同一個(gè),所以事務(wù)也不是同一個(gè)。
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
六、設(shè)計(jì)的表不支持事務(wù)
我們都知道,mysql5之前默認(rèn)的數(shù)據(jù)庫(kù)引擎是MyISAM,可能一些老的項(xiàng)目還在使用,但是他是不支持事務(wù)的
七、沒(méi)有開啟事務(wù)
如果創(chuàng)建的不是springboot項(xiàng)目可能會(huì)導(dǎo)致這樣的問(wèn)題出現(xiàn),因?yàn)閟pringboot項(xiàng)目有自動(dòng)裝配的類DataSourceTransactionManagerAutoConfiguration,已經(jīng)默認(rèn)開啟了事務(wù),配置spring.datasource參數(shù)就行,如果是spring項(xiàng)目,需要在applicationContext.xml配置的,不然事務(wù)不會(huì)生效
<!-- 配置事務(wù)管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 用切點(diǎn)把事務(wù)切進(jìn)去 -->
<aop:config>
<aop:pointcut expression="execution(* com.demo.*.*(..))" id="pointcut"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
還有就是切點(diǎn)配錯(cuò),也會(huì)導(dǎo)致事務(wù)失效
八、錯(cuò)誤的事務(wù)傳播
我們?cè)谑褂聾Transactional注解時(shí),是可以指定propagation參數(shù)的。
該參數(shù)的作用是指定事務(wù)的傳播特性,
spring目前支持7種傳播特性:
- REQUIRED 如果當(dāng)前上下文中存在事務(wù),那么加入該事務(wù),如果不存在事務(wù),創(chuàng)建一個(gè)事務(wù),這是默認(rèn)的傳播屬性值。
- SUPPORTS 如果當(dāng)前上下文存在事務(wù),則支持事務(wù)加入事務(wù),如果不存在事務(wù),則使用非事務(wù)的方式執(zhí)行。
- MANDATORY 如果當(dāng)前上下文中存在事務(wù),否則拋出異常。
- REQUIRES_NEW 每次都會(huì)新建一個(gè)事務(wù),并且同時(shí)將上下文中的事務(wù)掛起,執(zhí)行當(dāng)前新建事務(wù)完成以后,上下文事務(wù)恢復(fù)再執(zhí)行。
- NOT_SUPPORTED 如果當(dāng)前上下文中存在事務(wù),則掛起當(dāng)前事務(wù),然后新的方法在沒(méi)有事務(wù)的環(huán)境中執(zhí)行。
- NEVER 如果當(dāng)前上下文中存在事務(wù),則拋出異常,否則在無(wú)事務(wù)環(huán)境上執(zhí)行代碼。
- NESTED 如果當(dāng)前上下文中存在事務(wù),則嵌套事務(wù)執(zhí)行,如果不存在事務(wù),則新建事務(wù)。
如果我們?cè)谑謩?dòng)設(shè)置propagation參數(shù)的時(shí)候,把傳播特性設(shè)置錯(cuò)了
@Service
public class DemoService {
@Transactional(propagation = Propagation.NEVER)
public void query(Demo demo) {
}
}
我們可以看到query的事務(wù)傳播設(shè)置為了Propagation.NEVER,這種類型的傳播不支持事務(wù),會(huì)拋異常,
目前支持事務(wù)的三種傳播特性為:REQUIRED,REQUIRES_NEW,NESTED
九、自己捕獲了異常
事務(wù)不回滾,可能是我們?cè)趯懘a的時(shí)候自己在代碼手動(dòng)進(jìn)行了try…catch
@Transactional
public void query(Demo demo) {
try {
save(demo);
} catch (Exception e) {
System.out.println("異常");
}
}
這種情況下,spring事務(wù)不會(huì)進(jìn)行回滾,因?yàn)槲覀冞M(jìn)行了手動(dòng)捕獲異常,然后沒(méi)有手動(dòng)拋出,如果想要spring事務(wù)的正?;貪L,必須拋出它能處理的異常,如果沒(méi)有拋出異常,spring會(huì)認(rèn)為程序沒(méi)有問(wèn)題。
十、手動(dòng)拋出別的異常
我們沒(méi)有手動(dòng)捕獲異常,但是如果拋出的異常不正確,spring事務(wù)也不會(huì)回滾。
@Transactional
public void query(Demo demo) throws Exception{
try {
save(demo);
} catch (Exception e) {
throw new Exception(e);
}
}
上面我們捕獲了異常,然后手動(dòng)拋出Exception,事務(wù)同樣不會(huì)回滾,因?yàn)閟pring事務(wù),默認(rèn)情況下不會(huì)回滾Exception(非運(yùn)行時(shí)的異常),只會(huì)回滾RuntimeException(運(yùn)行時(shí)異常)和Error(錯(cuò)誤)。
十一、自定義回滾異常
在使用@Transactional注解聲明事務(wù)時(shí),有時(shí)我們想自定義回滾的異常,spring也是支持的??梢酝ㄟ^(guò)設(shè)置rollbackFor參數(shù),來(lái)完成這個(gè)功能。
但如果這個(gè)參數(shù)的值設(shè)置錯(cuò)了,就會(huì)引出一些問(wèn)題
@Transactional(rollbackFor = BusinessException.class)
public void query(Demo demo) throws Exception{
save(demo);
}
上面是我們自定義的業(yè)務(wù)異常,如果在執(zhí)行上面這段代碼,保存和更新數(shù)據(jù)時(shí),程序報(bào)錯(cuò)了,拋了SqlException、DuplicateKeyException等異常。而BusinessException是我們自定義的異常,報(bào)錯(cuò)的異常不屬于BusinessException,所以事務(wù)也不會(huì)回滾。即使rollbackFor有默認(rèn)值,但阿里巴巴開發(fā)者規(guī)范中,還是要求開發(fā)者重新指定該參數(shù)。
因?yàn)槿绻褂媚J(rèn)值,一旦程序拋出了Exception,事務(wù)不會(huì)回滾,這會(huì)出現(xiàn)很大的bug。所以,建議一般情況下,將該參數(shù)設(shè)置成:Exception或Throwable。
十二、嵌套事務(wù)回滾過(guò)頭
@Service
public class DemoService {
@Autowired
private DemoTwoService demoTwoService;
@Autowired
private DemoDao demoDao;
public
@Transactional(rollbackFor = Exception.class)
public void query(Demo demo) throws Exception{
demoDao.save(demo);
demoTwoService.save(demo);
}
}
原本只是希望回滾demoServise.save(),不回滾demoDao.save(demo);,但是這種情況兩個(gè)都會(huì)被回滾,因?yàn)閐emoTwoService.save(demo);沒(méi)有捕獲異常,往上拋出,導(dǎo)致query進(jìn)行回滾,所以同時(shí)回滾了兩個(gè)
解決方法:
@Service
public class DemoService {
@Autowired
private DemoTwoService demoTwoService;
@Autowired
private DemoDao demoDao;
public
@Transactional(rollbackFor = Exception.class)
public void query(Demo demo) throws Exception{
demoDao.save(demo);
try {
demoTwoService.save(demo);
} finally {
}
}
}
可以將內(nèi)部嵌套事務(wù)放在try/catch中,并且不繼續(xù)往上拋異常。這樣就能保證,如果內(nèi)部嵌套事務(wù)中出現(xiàn)異常,只回滾內(nèi)部事務(wù),而不影響外部事務(wù)。
十三、編程式事務(wù)
上面的事務(wù)失效都是基于@Transactional注解的,我們把這種事務(wù)叫做:聲明式事務(wù)。
其實(shí),spring還提供了另外一種創(chuàng)建事務(wù)的方式,即通過(guò)手動(dòng)編寫代碼實(shí)現(xiàn)的事務(wù),我們把這種事務(wù)叫做:編程式事務(wù)。
@Service
public class DemoService {
@Autowired
private TransactionTemplate transactionTemplate;
@Transactional(rollbackFor = Exception.class)
public void query(Demo demo) throws Exception{
transactionTemplate.execute((status -> {
save(demo);
return Boolean.TRUE;
}));
}
@Transactional
public void save(Demo demo) {
}
}
在spring中為了支持編程式事務(wù),專門提供了一個(gè)類:TransactionTemplate,在它的execute方法中,就實(shí)現(xiàn)了事務(wù)的功能。
使用TransactionTemplate的編程式事務(wù)可以
- 避免由于spring aop問(wèn)題,導(dǎo)致事務(wù)失效的問(wèn)題。
- 能夠更小粒度的控制事務(wù)的范圍。
tips:關(guān)于本文舉例的query加事務(wù)不太符合開發(fā)的要求,但是有點(diǎn)懶就不改了
到此這篇關(guān)于Spring事務(wù)失效的各種場(chǎng)景(13種) 的文章就介紹到這了,更多相關(guān)Spring 事務(wù)失效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring根據(jù)URL參數(shù)進(jìn)行路由的方法詳解
這篇文章主要給大家介紹了關(guān)于Spring根據(jù)URL參數(shù)進(jìn)行路由的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起來(lái)看看吧。2017-12-12
MyBatis攔截器動(dòng)態(tài)替換表名的方法詳解
因?yàn)槲覀兂志脤涌蚣芨嗟厥褂肕yBatis,那我們就借助于MyBatis的攔截器來(lái)完成我們的功能,這篇文章主要給大家介紹了關(guān)于MyBatis攔截器動(dòng)態(tài)替換表名的相關(guān)資料,需要的朋友可以參考下2022-04-04
Java隊(duì)列數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)
這篇文章主要介紹了Java隊(duì)列數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn),隊(duì)列是一種特殊的線性表,只允許在表的隊(duì)頭進(jìn)行刪除操作,在表的后端進(jìn)行插入操作,隊(duì)列是一個(gè)有序表先進(jìn)先出,想了解更多相關(guān)資料的小伙伴可以參考下面文章的詳細(xì)內(nèi)容2021-12-12
深入了解JAVA數(shù)據(jù)類型與運(yùn)算符
這篇文章主要介紹了Java基本數(shù)據(jù)類型和運(yùn)算符,結(jié)合實(shí)例形式詳細(xì)分析了java基本數(shù)據(jù)類型、數(shù)據(jù)類型轉(zhuǎn)換、算術(shù)運(yùn)算符、邏輯運(yùn)算符等相關(guān)原理與操作技巧,需要的朋友可以參考下2021-07-07
java結(jié)合keytool如何實(shí)現(xiàn)非對(duì)稱簽名和驗(yàn)證詳解
這篇文章主要給大家介紹了關(guān)于java結(jié)合keytool如何實(shí)現(xiàn)非對(duì)稱簽名和驗(yàn)證的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
Java Servlet線程中AsyncContext異步處理Http請(qǐng)求
這篇文章主要介紹了Java Servlet線程中AsyncContext異步處理Http請(qǐng)求及在業(yè)務(wù)中應(yīng)用,AsyncContext是Servlet 3.0使Servlet 線程不再需要一直阻塞,直到業(yè)務(wù)處理完畢才能再輸出響應(yīng),最后才結(jié)束該Servlet線程2023-03-03
用Java實(shí)現(xiàn)小球碰壁反彈的簡(jiǎn)單實(shí)例(算法十分簡(jiǎn)單)
下面小編就為大家?guī)?lái)一篇用Java實(shí)現(xiàn)小球碰壁反彈的簡(jiǎn)單實(shí)例(算法十分簡(jiǎn)單)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08

