最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring事務(wù)失效的問題及解決方案

 更新時間:2025年11月05日 08:51:35   作者:張 先生  
本文詳細分析了Spring事務(wù)失效的原因,并提供了一個解決方案,在代碼中,事務(wù)傳播屬性設(shè)置為REQUIRES_NEW解決了事務(wù)失效的問題,確保了日志記錄的操作不會影響主事務(wù)的回滾,同時,文中還總結(jié)了各種事務(wù)傳播屬性的含義和使用場景

Spring事務(wù)失效問題

開場

Spring事務(wù)管理,在Service實現(xiàn)類的方法上添加@Transactional注解就能進行事務(wù)控制,但是最近遇到一個事務(wù)失效的問題,淺淺分析一下

問題

在開發(fā)過程中發(fā)現(xiàn)這樣一個bug,有一段邏輯代碼:商品入庫、并且生成一個入庫單的操作;首先肯定的是需要操作兩張表,一是 入庫加庫存,二是 新增一個入庫單類似于入庫記錄。但是測試時候遇到一個問題,有一個商品入庫成功了,庫存也加進去了,但是沒有生成入庫單,我立馬想到的是難道是事務(wù)沒有控制好?翻看接口代碼發(fā)現(xiàn)還真是。。。

Spring事務(wù)失效的原因

我們都知道@Transactional失效的原因有多種,列舉一下,后面有時間再仔細分析研究

1.方法訪問修飾符:

  • @Transactional注解只對public方法生效。
  • 如果事務(wù)性方法是privateprotected,事務(wù)不會生效

2.類內(nèi)部方法調(diào)用:

  • 在同一個類中調(diào)用標(biāo)注了**@Transactional**的方法(即自調(diào)用)時,事務(wù)管理器不會介入,因為Spring AOP代理無法攔截內(nèi)部調(diào)用

3.沒有啟用事務(wù)管理:

  • 必須顯式啟用Spring的事務(wù)管理,通常使用**@EnableTransactionManagement**注解在配置類上,或者在XML中配置使用
<tx:annotation-driven/>

4.事務(wù)傳播屬性不當(dāng):

  • 事務(wù)傳播屬性(Propagetion)配置不當(dāng)會導(dǎo)致事務(wù)失效。
  • 例如:傳播行為REQUIRES_NEW會掛起當(dāng)前事務(wù),創(chuàng)建一個新事物,這可能不是我們想要的

5.異常處理不當(dāng):

  • 默認情況下,Spring事務(wù)管理只在運行時異常(RuntimeExpection及其子類)和錯誤(Error及其子類)時回滾。
  • 如果捕獲并處理了一場,但沒有重新拋出,事務(wù)不會回滾。此外需要注意,對于檢查異常(Exception及其子類),需要制定rollbackFor屬性來觸發(fā)事務(wù)回滾,例如:
@Transactional(rollbackFor = Exception.class)

6.多線程環(huán)境:

  • Transactional 注解無法再多線程環(huán)境中傳播事務(wù)。
  • Spring事務(wù)管理依賴于線程局部變量(ThreadLocal),在不同獻策會給你之間共享事務(wù)需要顯式處理。(后面遇到的話仔細研究一下)

7.數(shù)據(jù)源和事務(wù)管理器配置不一致:

  • 確保數(shù)據(jù)源(DataSource)和事務(wù)管理器(TransactionManager)配置一致。
  • 如果有多個數(shù)據(jù)源和事務(wù)管理器,需要明確指定使用的事務(wù)管理器。

8.Spring代理模式限制:

  • Spring默認使用AOP代理來管理事務(wù)。
  • 默認情況下使用JDK動態(tài)代理,只有實現(xiàn)接口的類可以使用事務(wù)。
  • 如果沒有實現(xiàn)接口,需要使用CGLIB代理,通過proxyTargetClass=true啟用CGLIB代理。

9.異步方法:

  • 一步方法(如使用@Async注解的方法)運行在獨立線程中,不會參與當(dāng)前線程的事務(wù)管理。
  • 需要特別處理異步方法的事務(wù)管理。

排查bug

言歸正傳,我在這段邏輯代碼中發(fā)現(xiàn)有兩個問題,首先代碼塊被 try catch 捕獲后沒有重新拋出異常,而且這段代碼在catch 里面有日志記錄,插入到一張日志表;業(yè)務(wù)邏輯太多此處就不貼真實代碼了,但是我寫了一個demo是可以復(fù)刻問題的。

代碼如下:

@Transactional(rollbackFor = Exception.class)
public User update(User userDto) {
    try {
        //查詢用戶
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(StrUtil.isNotBlank(userDto.getId()), User::getId, userDto.getId());
        User user = userDao.selectOne(queryWrapper);
        //修改數(shù)據(jù):扣錢
        user.setMoney(user.getMoney().subtract(userDto.getMoney()));
        LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(StrUtil.isNotBlank(userDto.getId()), User::getId, userDto.getId());
        userDao.update(user);
        //模擬異常
        int i = 1/0;
    } catch (Exception e) {
        log.error(e.getMessage(),e);
        //日志記錄:將錯誤信息插入到日志表
        insertLog(userDto, e);
    }
    return null;
}

    /*
    * 日志記錄的方法
    */
    public void recordLog(Exception e) {
        LogHistory logHistory = new LogHistory();
        logHistory.setCode("1");
        logHistory.setTime(new Date());
        logHistory.setErrorMsg(e.getMessage());
        logHistoryMapper.insert(logHistory);
    }

上面這段代碼,大家還有發(fā)現(xiàn)問題其他嗎,細心的小伙伴一定還能發(fā)現(xiàn)其他問題,那就是這個 catch 捕獲異常后并沒有拋出,所以這段代碼并不會回滾,但是問題來了,如果我們在 catch 中最后拋出異常,那么日志記錄的操作也將回滾,這里就會有點沖突,如果代碼有異常報錯,我們的目的就是要回滾,但是還不能把日志記錄給回滾。

解決方案

發(fā)現(xiàn)了問題,該如何去解決呢?

在這里我用到了一個事務(wù)的傳播行為:先說結(jié)論,將事務(wù)的傳播屬性設(shè)置為REQUIRES_NEW,就是在當(dāng)前事務(wù)中新建了一個事務(wù),用新的事務(wù)去控制我日志記錄的操作,這樣的話兩個事務(wù)互不影響,A事務(wù)如果拋出異常,不影響B(tài)事務(wù),B事務(wù)正常插入數(shù)據(jù),完美解決!當(dāng)然不止這一種解決方案,還有異步調(diào)用,mq發(fā)消息等方式。

但是需要注意,我們需要在單獨的Service實現(xiàn)類中去編寫日志記錄的方法,不能在A事務(wù)中去編寫了,代碼如下:

    @Transactional(rollbackFor = Exception.class)
    public User update(User userDto) {
        try {
            //查詢用戶
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(StrUtil.isNotBlank(userDto.getId()), User::getId, userDto.getId());
            User user = userDao.selectOne(queryWrapper);
            //修改數(shù)據(jù):扣錢
            user.setMoney(user.getMoney().subtract(userDto.getMoney()));
            LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(StrUtil.isNotBlank(userDto.getId()), User::getId, userDto.getId());
            userDao.update(user);
            //模擬異常
            int i = 1/0;
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            //日志記錄:需要在另外的service中編寫
            logHistoryService.insertLog(userDto, e);
            //拋出異常
            throw e;
        }
        return null;
    }
    /*
    * 日志記錄
    * 此處我們需要設(shè)置事務(wù)的傳播屬性為 REQUIRES_NEW
    */
	@Transactional(propagation = Propagation.REQUIRES_NEW)
    public LogHistory insertLog(User userDto, Exception e) {
        LogHistory logHistory = new LogHistory();
        logHistory.setCode("111");
        logHistory.setTime(new Date());
        logHistory.setErrorMsg(e.getMessage());
        logHistoryMapper.insert(logHistory);
        return logHistory;
    }

事務(wù)的傳播屬性

在 Spring 中,事務(wù)的傳播屬性(Propagation)決定了事務(wù)的行為在方法間調(diào)用時的傳播方式。傳播屬性通過 @Transactional 注解的 propagation 屬性來設(shè)置,Spring 提供了以下幾種傳播屬性:

1.REQUIRED(默認值):

  • 如果當(dāng)前已經(jīng)存在一個事務(wù),則加入該事務(wù)。
  • 如果當(dāng)前沒有事務(wù),則創(chuàng)建一個新的事務(wù)。
  • 這是最常見的傳播行為。

2.REQUIRES_NEW

  • 無論當(dāng)前是否存在事務(wù),總是創(chuàng)建一個新的事務(wù)。
  • 如果當(dāng)前存在事務(wù),則掛起當(dāng)前事務(wù),直到新事務(wù)完成。

3.SUPPORTS

  • 如果當(dāng)前存在事務(wù),則加入該事務(wù)。
  • 如果當(dāng)前沒有事務(wù),則以非事務(wù)方式執(zhí)行。

4.NOT_SUPPORTED

  • 以非事務(wù)方式執(zhí)行操作。
  • 如果當(dāng)前存在事務(wù),則掛起當(dāng)前事務(wù),直到當(dāng)前操作完成。

5.MANDATORY

  • 必須在一個現(xiàn)有事務(wù)中運行。
  • 如果當(dāng)前沒有事務(wù),則拋出異常。

6.NEVER

  • 必須在非事務(wù)上下文中運行。
  • 如果當(dāng)前存在事務(wù),則拋出異常。

7.NESTED

  • 如果當(dāng)前存在事務(wù),則在嵌套事務(wù)中運行。
  • 如果當(dāng)前沒有事務(wù),則創(chuàng)建一個新的事務(wù)。
  • 嵌套事務(wù)使用保存點(savepoint),如果嵌套事務(wù)回滾,它只回滾到保存點,外部事務(wù)可以繼續(xù)。
  • 如果當(dāng)前沒有事務(wù),則拋出異常。

8.NEVER

  • 必須在非事務(wù)上下文中運行。
  • 如果當(dāng)前存在事務(wù),則拋出異常。

9.NESTED

  • 如果當(dāng)前存在事務(wù),則在嵌套事務(wù)中運行。
  • 如果當(dāng)前沒有事務(wù),則創(chuàng)建一個新的事務(wù)。
  • 嵌套事務(wù)使用保存點(savepoint),如果嵌套事務(wù)回滾,它只回滾到保存點,外部事務(wù)可以繼續(xù)。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論

永福县| 上杭县| 承德县| 榆树市| 建湖县| 淳化县| 铜陵市| 石嘴山市| 新蔡县| 洞头县| 海林市| 嘉定区| 海宁市| 鄂尔多斯市| 兴义市| 娄烦县| 兴和县| 松溪县| 麦盖提县| 历史| 南投市| 延安市| 黄石市| 辽阳县| 仪征市| 衡南县| 海门市| 海城市| 宁远县| 凤城市| 沅江市| 新建县| 庆阳市| 内江市| 佛冈县| 无极县| 花莲市| 镇沅| 绍兴县| 江都市| 噶尔县|