0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Spring事務(wù)失效的十種常見場景

jf_ro2CN3Fa ? 來源:稀土掘金技術(shù)社區(qū) ? 2023-12-11 15:03 ? 次閱讀

1 概述

Spring針對Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API(JPA)等事務(wù) API,實現(xiàn)了一致的編程模型,而Spring的聲明式事務(wù)功能更是提供了極其方便的事務(wù)配置方式,配合Spring Boot的自動配置,大多數(shù)Spring Boot項目只需要在方法上標記@Transactional注解,即可一鍵開啟方法的事務(wù)性配置。

但是,事務(wù)如果沒有被正確出,很有可能會導(dǎo)致事務(wù)的失效,帶來意想不到的數(shù)據(jù)不一致問題,隨后就是大量的人工接入查看和修復(fù)數(shù)據(jù),該篇主要分享Spring事務(wù)在技術(shù)上的正確使用方式,避免因為事務(wù)處理不當(dāng)導(dǎo)致業(yè)務(wù)邏輯產(chǎn)生大量偶發(fā)性BUG。

在分析事務(wù)失效的常見場景之前,我們先來了解一下:事務(wù)的傳播類型@Transactionnal 注解的不同屬性的含義。

事務(wù)的傳播類型

//如果有事務(wù),那么加入事務(wù),沒有的話新建一個(默認)
@Transactional(propagation=Propagation.REQUIRED)
//容器不為這個方法開啟事務(wù)
@Transactional(propagation=Propagation.NOT_SUPPORTED)
//不管是否存在事務(wù),都創(chuàng)建一個新的事務(wù),原來的掛起,新的執(zhí)行完畢,繼續(xù)執(zhí)行老的事務(wù)
@Transactional(propagation=Propagation.REQUIRES_NEW)
//必須在一個已有的事務(wù)中執(zhí)行,否則拋出異常
@Transactional(propagation=Propagation.MANDATORY)
//必須在一個沒有的事務(wù)中執(zhí)行,否則拋出異常(與Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.NEVER)
//如果其他bean調(diào)用這個方法,在其他bean中聲明事務(wù),那就用事務(wù),如果其他bean沒有聲明事務(wù),那就不用事務(wù)
@Transactional(propagation=Propagation.SUPPORTS)

isolation

該屬性用于設(shè)置底層數(shù)據(jù)庫的事務(wù)隔離級別,事務(wù)的隔離級別介紹:

//讀取未提交數(shù)據(jù)(會出現(xiàn)臟讀,不可重復(fù)讀)基本不使用
@Transactional(isolation=Isolation.READ_UNCOMMITTED)
//讀取已提交數(shù)據(jù)(會出現(xiàn)不可重復(fù)讀和幻讀)Oracle默認
@Transactional(isolation=Isolation.READ_COMMITTED)
//可重復(fù)讀(會出現(xiàn)幻讀)MySQL默認
@Transactional(isolation=Isolation.REPEATABLE_READ)
//串行化
@Transactional(isolation=Isolation.SERIALIZABLE)

@Transactionnal注解屬性

@Transactional注解可以作用于接口、接口方法、類以及類方法上,它可以通過不同的參數(shù)來選擇什么類型Exception異常下執(zhí)行回滾或者不回滾操作。

d4855e8a-9640-11ee-8b88-92fbcf53809c.png

1. 事務(wù)方法未被Spring管理

如果事務(wù)方法所在的類沒有注冊到Spring IOC容器中,也就是說,事務(wù)方法所在類并沒有被Spring管理,則Spring事務(wù)會失效,舉個例子:

publicclassProductServiceImplextendsServiceImplimplementsIProductService{

@Autowired
privateProductMapperproductMapper;

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
publicvoidupdateProductStockById(IntegerstockCount,LongproductId){
productMapper.updateProductStockById(stockCount,productId);
}
}

ProductServiceImpl實現(xiàn)類上沒有添加@Service注解,Product的實例也就沒有被加載到Spring IOC容器,此時updateProductStockById()方法的事務(wù)就會在Spring中失效。

2. 方法使用final類型修飾

有時候,某個方法不想被子類重新,這時可以將該方法定義成final的。普通方法這樣定義是沒問題的,但如果將事務(wù)方法定義成final,例如:

@Service
publicclassOrderServiceImpl{

@Transactional
publicfinalvoidcancel(OrderDTOorderDTO){
//取消訂單
cancelOrder(orderDTO);
}
}

OrderServiceImpl的cancel取消訂單方法被final修飾符修飾,Spring事務(wù)底層使用了AOP,也就是通過JDK動態(tài)代理或者cglib,幫我們生成了代理類,在代理類中實現(xiàn)的事務(wù)功能。但如果某個方法用final修飾了,那么在它的代理類中,就無法重寫該方法,從而無法添加事務(wù)功能。這種情況事務(wù)就會在Spring中失效。

Tips: 如果某個方法是static的,同樣無法通過動態(tài)代理將方法聲明為事務(wù)方法。

3. 非public修飾的方法

如果事務(wù)方式不是public修飾,此時Spring事務(wù)會失效,舉個例子:

@Service
publicclassProductServiceImplextendsServiceImplimplementsIProductService{

@Autowired
privateProductMapperproductMapper;

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
privatevoidupdateProductStockById(IntegerstockCount,StringproductId){
productMapper.updateProductStockById(stockCount,productId);
}
}

雖然ProductServiceImpl添加了@Service注解,同時updateProductStockById()方法上添加了@Transactional(propagation = Propagation.REQUIRES_NEW)注解,但是由于事務(wù)方法updateProductStockById()被 private 定義為方法內(nèi)私有,同樣Spring事務(wù)會失效。

4. 同一個類中的方法相互調(diào)用

@Service
publicclassOrderServiceImplextendsServiceImplimplementsIOrderService{
@Autowired
privateOrderMapperorderMapper;
@Autowired
privateProductMapperproductMapper;

@Override
publicResponseEntitysubmitOrder(Orderorder){
//保存生成訂單信息
longorderNo=Math.abs(ThreadLocalRandom.current().nextLong(1000));
order.setOrderNo("ORDER_"+orderNo);
orderMapper.insert(order);

//扣減庫存
this.updateProductStockById(order.getProductId(),1L);
returnnewResponseEntity(HttpStatus.OK);
}

@Transactional(propagation=Propagation.REQUIRES_NEW)
publicvoidupdateProductStockById(Integernum,LongproductId){
productMapper.updateProductStockById(num,productId);
}
}

submitOrder()方法和updateProductStockById()方法都在OrderService類中,然而submitOrder()方法沒有添加事務(wù)注解,updateProductStockById()方法雖然添加了事務(wù)注解,這種情況updateProductStockById()會在Spring事務(wù)中失效。

5. 方法的事務(wù)傳播類型不支持事務(wù)

如果內(nèi)部方法的事務(wù)傳播類型為不支持事務(wù)的傳播類型,則內(nèi)部方法的事務(wù)同樣會在Spring中失效,舉個例子:

@Service
publicclassOrderServiceImplextendsServiceImplimplementsIOrderService{
@Autowired
privateOrderMapperorderMapper;
@Autowired
privateProductMapperproductMapper;

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
publicResponseEntitysubmitOrder(Orderorder){
longorderNo=Math.abs(ThreadLocalRandom.current().nextLong(1000));
order.setOrderNo("ORDER_"+orderNo);
orderMapper.insert(order);

//扣減庫存
this.updateProductStockById(order.getProductId(),1L);
returnnewResponseEntity(HttpStatus.OK);
}


/**
*扣減庫存方法事務(wù)類型聲明為NOT_SUPPORTED不支持事務(wù)的傳播
*/
@Transactional(propagation=Propagation.NOT_SUPPORTED)
publicvoidupdateProductStockById(Integernum,LongproductId){
productMapper.updateProductStockById(num,productId);
}
}

6. 異常被內(nèi)部catch,程序生吞異常

@Service
publicclassOrderServiceImplextendsServiceImplimplementsIOrderService{
@Autowired
privateOrderMapperorderMapper;
@Autowired
privateProductMapperproductMapper;

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
publicResponseEntitysubmitOrder(Orderorder){
longorderNo=Math.abs(ThreadLocalRandom.current().nextLong(1000));
order.setOrderNo("ORDER_"+orderNo);
orderMapper.insert(order);

//扣減庫存
this.updateProductStockById(order.getProductId(),1L);
returnnewResponseEntity(HttpStatus.OK);
}

/**
*扣減庫存方法事務(wù)類型聲明為NOT_SUPPORTED不支持事務(wù)的傳播
*/
@Transactional(propagation=Propagation.NOT_SUPPORTED)
publicvoidupdateProductStockById(Integernum,LongproductId){
try{
productMapper.updateProductStockById(num,productId);
}catch(Exceptione){
//這里僅僅是捕獲異常之后的打?。ㄏ喈?dāng)于程序吞掉了異常)
log.error("ErrorupdatingproductStock:{}",e);
}
}
}

7. 數(shù)據(jù)庫不支持事務(wù)

Spring事務(wù)生效的前提是連接的數(shù)據(jù)庫支持事務(wù),如果底層的數(shù)據(jù)庫都不支持事務(wù),則Spring事務(wù)肯定會失效的,例如:使用MySQL數(shù)據(jù)庫,選用MyISAM存儲引擎,因為MyISAM存儲引擎本身不支持事務(wù),因此事務(wù)毫無疑問會失效。

8. 未配置開啟事務(wù)

如果項目中沒有配置Spring的事務(wù)管理器,即使使用了Spring的事務(wù)管理功能,Spring的事務(wù)也不會生效,例如,如果你是Spring Boot項目,沒有在SpringBoot項目中配置如下代碼:

@Bean
publicPlatformTransactionManagertransactionManager(DataSourcedataSource){
returnnewDataSourceTransactionManager(dataSource);
}

如果是以往的Spring MVC項目,如果沒有配置下面的代碼,Spring事務(wù)也不會生效,正常需要在applicationContext.xml文件中,手動配置事務(wù)相關(guān)參數(shù),比如:

 








 




9. 錯誤的傳播特性

其實,我們在使用@Transactional注解時,是可以指定propagation參數(shù)的。

該參數(shù)的作用是指定事務(wù)的傳播特性,目前Spring支持7種傳播特性:

REQUIRED 如果當(dāng)前上下文中存在事務(wù),那么加入該事務(wù),如果不存在事務(wù),創(chuàng)建一個事務(wù),這是默認的傳播屬性值。

SUPPORTS 如果當(dāng)前上下文存在事務(wù),則支持事務(wù)加入事務(wù),如果不存在事務(wù),則使用非事務(wù)的方式執(zhí)行。

MANDATORY 如果當(dāng)前上下文中存在事務(wù),否則拋出異常。

REQUIRES_NEW 每次都會新建一個事務(wù),并且同時將上下文中的事務(wù)掛起,執(zhí)行當(dāng)前新建事務(wù)完成以后,上下文事務(wù)恢復(fù)再執(zhí)行。

NOT_SUPPORTED 如果當(dāng)前上下文中存在事務(wù),則掛起當(dāng)前事務(wù),然后新的方法在沒有事務(wù)的環(huán)境中執(zhí)行。

NEVER 如果當(dāng)前上下文中存在事務(wù),則拋出異常,否則在無事務(wù)環(huán)境上執(zhí)行代碼。

NESTED 如果當(dāng)前上下文中存在事務(wù),則嵌套事務(wù)執(zhí)行,如果不存在事務(wù),則新建事務(wù)。

如果我們在手動設(shè)置propagation參數(shù)的時候,把傳播特性設(shè)置錯了,比如:

@Service
publicclassOrderServiceImpl{

@Transactional(propagation=Propagation.NEVER)
publicvoidcancelOrder(UserModeluserModel){
//取消訂單
cancelOrder(orderDTO);
//還原庫存
restoreProductStock(orderDTO.getProductId(),orderDTO.getProductCount());
}
}

我們可以看到cancelOrder()方法的事務(wù)傳播特性定義成了Propagation.NEVER,這種類型的傳播特性不支持事務(wù),如果有事務(wù)則會拋異常。

10. 多線程調(diào)用

在實際項目開發(fā)中,多線程的使用場景還是挺多的。如果Spring事務(wù)用在多線程場景中使用不當(dāng),也會導(dǎo)致事務(wù)無法生效。

@Slf4j
@Service
publicclassOrderServiceImpl{

@Autowired
privateOrderMapperorderMapper;
@Autowired
privateMessageServicemessageService;

@Transactional
publicvoidorderCommit(orderModelorderModel)throwsException{
orderMapper.insertOrder(orderModel);
newThread(()->{
messageService.sendSms();
}).start();
}
}

@Service
publicclassMessageService{

@Transactional
publicvoidsendSms(){
//發(fā)送短信
}
}

通過示例,我們可以看到訂單提交的事務(wù)方法orderCommit()中,調(diào)用了發(fā)送短信的事務(wù)方法sendSms(),但是發(fā)送短信的事務(wù)方法sendSms()是另起了一個線程調(diào)用的。

這樣會導(dǎo)致兩個方法不在同一個線程中,從而是兩個不同的事務(wù)。如果是sendSms()方法中拋了異常,orderCommit()方法也回滾是不可能的。

實際上,Spring的事務(wù)是通過ThreadLocal來保證線程安全的,事務(wù)和當(dāng)前線程綁定,多個線程自然會讓事務(wù)失效。

d49a85c6-9640-11ee-8b88-92fbcf53809c.jpg

2 總結(jié)

本篇文章主要是介紹Spring事務(wù)傳播特性,闡明了@Transactional注解屬性的使用方式,通過不同的代碼示例演示了Spring事務(wù)失效的常見場景。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 接口
    +關(guān)注

    關(guān)注

    33

    文章

    8254

    瀏覽量

    149948
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4670

    瀏覽量

    67764
  • spring
    +關(guān)注

    關(guān)注

    0

    文章

    335

    瀏覽量

    14258
  • 編程模型
    +關(guān)注

    關(guān)注

    0

    文章

    7

    瀏覽量

    1387

原文標題:Spring 事務(wù)失效的十種常見場景

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    十種精密全波整流電路

    十種精密全波整流電路
    發(fā)表于 08-09 15:09

    請問怎么做一個跑馬燈有十種模式,第十種模式有三音樂,可加速減速和無線遙控?

    我現(xiàn)在想做一個跑馬燈,這個跑馬燈有十種模式,第十種模式要求有三音樂。,還得有數(shù)碼管顯示第幾種模式。可以無線遙控。求哪位大神可以幫我。小女子必有重謝。
    發(fā)表于 07-19 04:49

    Spring的兩方式事務(wù)管理和API接口介紹

    Spring事務(wù)管理
    發(fā)表于 03-21 06:52

    Spring事務(wù)分析的實現(xiàn)方式

    Spring事務(wù)原理分析
    發(fā)表于 07-02 15:19

    詳解Spring事務(wù)管理

    在學(xué)習(xí)spring事務(wù)管理時,我忍不住要問,spring為什么進行事務(wù)管理,spring怎么進行的事務(wù)
    發(fā)表于 07-12 06:54

    Spring事務(wù)管理詳解說明

    Spring事務(wù)管理詳解
    發(fā)表于 05-20 13:46

    啟動Spring Boot項目應(yīng)用的三方法

    基礎(chǔ)。我們知道了Spring Boot是個什么了,那么我們又該如何啟動Spring Boot應(yīng)用呢?這里小編給大家推薦常用的三方法。分別是IDEA編輯器啟動、命令啟動、java命令jar文件啟動。下面
    發(fā)表于 01-14 17:33

    十種精密全波整流電路原圖分享

    十種精密全波整流電路原圖,大家點評下
    發(fā)表于 11-27 06:47

    十種方法能保護云數(shù)據(jù)安全

    十種方法能保護云數(shù)據(jù)安全
    發(fā)表于 01-14 12:00 ?12次下載

    淺談Spring事務(wù)的那些坑

    對于從事java開發(fā)工作的同學(xué)來說,spring事務(wù)肯定再熟悉不過了。在某些業(yè)務(wù)場景下,如果同時有多張表的寫入操作,為了保證操作的原子性(要么同時成功,要么同時失?。┍苊鈹?shù)據(jù)不一致的情況,我們一般都會使用
    的頭像 發(fā)表于 10-11 10:31 ?661次閱讀

    淺談Spring事務(wù)底層原理

    開啟Spring事務(wù)本質(zhì)上就是增加了一個Advisor,但我們使用@EnableTransactionManagement注解來開啟Spring事務(wù)是,該注解代理的功能就是向
    的頭像 發(fā)表于 12-06 09:56 ?616次閱讀

    十種常見的濾波器分享

    運算放大器可以用于設(shè)計各種類型的濾波器,以下是十種常見的濾波器: 1. 低通濾波器:能夠通過讓低于截止頻率的信號通過,而抑制高于截止頻率的信號。
    的頭像 發(fā)表于 04-24 10:44 ?6221次閱讀

    8個Spring事務(wù)失效場景介紹

    作為Java開發(fā)工程師,相信大家對Spring事務(wù)的使用并不陌生。但是你可能只是停留在基礎(chǔ)的使用層面上,在遇到一些比較特殊的場景,事務(wù)可能
    的頭像 發(fā)表于 05-11 10:41 ?507次閱讀
    8個<b class='flag-5'>Spring</b><b class='flag-5'>事務(wù)</b><b class='flag-5'>失效</b>的<b class='flag-5'>場景</b>介紹

    spring事務(wù)失效的一些場景

    對于從事java開發(fā)工作的同學(xué)來說,spring事務(wù)肯定再熟悉不過了。 在某些業(yè)務(wù)場景下,如果一個請求中,需要同時寫入多張表的數(shù)據(jù)。為了保證操作的原子性(要么同時成功,要么同時失敗),避免數(shù)據(jù)
    的頭像 發(fā)表于 10-08 14:27 ?381次閱讀
    <b class='flag-5'>spring</b><b class='flag-5'>事務(wù)</b><b class='flag-5'>失效</b>的一些<b class='flag-5'>場景</b>

    Spring事務(wù)傳播性的相關(guān)知識

    本文主要介紹了Spring事務(wù)傳播性的相關(guān)知識。
    的頭像 發(fā)表于 01-10 09:29 ?303次閱讀
    <b class='flag-5'>Spring</b><b class='flag-5'>事務(wù)</b>傳播性的相關(guān)知識