中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring事務@Transactional注解不生效怎么解決

發布時間:2022-07-21 17:01:51 來源:億速云 閱讀:378 作者:iii 欄目:開發技術


背景

在我們工作中,經常會用到 @Transactional 聲明事務,不正確的使用姿勢會導致注解失效,下面就來分析四種最常見的@Transactional事務不生效的 Case:

類內部訪問:A 類的 a1 方法沒有標注 @Transactional,a2 方法標注 @Transactional,在 a1 里面調用 a2;

私有方法:將 @Transactional 注解標注在非 public 方法上;

異常不匹配:@Transactional 未設置 rollbackFor 屬性,方法返回 Exception 等異常;

多線程:主線程和子線程的調用,線程拋出異常。

示例代碼

UserDao 接口,操作數據庫;UserController 實現業務邏輯,聲明事務,調用 UserController.testSuccess 方法,事務聲明生效

// 提供的接口
public interface UserDao {
    // select * from user_test where uid = "#{uid}"
    public MyUser selectUserById(Integer uid);
    // update user_test set uname =#{uname},usex = #{usex} where uid = #{uid}
    public int updateUser(MyUser user);
}
@Service
public class UserController {
    @Autowired
    private UserDao userDao;
    public void update(Integer id) {
        MyUser user = new MyUser();
        user.setUid(id);
        user.setUname("張三-testing");
        user.setUsex("女");
        userDao.updateUser(user);
    }
    public MyUser query(Integer id) {
        MyUser user = userDao.selectUserById(id);
        return user;
    }
    // 正常情況
    @Transactional(rollbackFor = Exception.class)
    public void testSuccess() throws Exception {
        Integer id = 1;
        MyUser user = query(id);
        System.out.println("原記錄:" + user);
        update(id);
        throw new Exception("事務生效");
    }
}

 為了快速說明問題,直接在controller中實現了業務邏輯和事務聲明,不代表生產環境中的代碼分層

1. 類內部訪問

在類 UserController 中新增一個方法 testInteralCall():

public void testInteralCall() throws Exception {
    testSuccess();
    throw new Exception("事務不生效:類內部訪問");
}

這里 testInteralCall() 沒有標注 @Transactional,我們再看一下測試用例:

public static void main(String[] args) throws Exception {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserController uc = (UserController) applicationContext.getBean("userController");
    try {
        uc.testSuccess();
    } finally {
        MyUser user =  uc.query(1);
        System.out.println("修改后的記錄:" + user);
    }
}
// 輸出:
// 原記錄:MyUser(uid=1, uname=張三, usex=女)
// 修改后的記錄:MyUser(uid=1, uname=張三-testing, usex=女)

從上面的輸出可以看到,事務并沒有回滾,這個是什么原因呢?

因為 @Transactional 的工作機制是基于 AOP 實現,AOP 是使用動態代理實現的,如果通過代理直接調用 testSuccess(),通過 AOP 會前后進行增強,增強的邏輯其實就是在 testSuccess() 的前后分別加上開啟、提交事務的邏輯。

現在是通過 testInteralCall() 去調用 testSuccess(),testSuccess() 前后不會進行任何增強操作,也就是類內部調用,不會通過代理方式訪問。

2. 私有方法

在私有方法上,添加 @Transactional 注解也不會生效:

@Transactional(rollbackFor = Exception.class)
private void testPirvateMethod() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原記錄:" + user);
    update(id);
    throw new Exception("測試事務生效");
}

直接使用時,下面這種場景不太容易出現,因為 IDEA 會有提醒,文案為: Methods annotated with '@Transactional' must be overridable,至于深層次的原理,源碼部分會給你解讀。

3. 異常不匹配

這里的 @Transactional 沒有設置 rollbackFor = Exception.class 屬性:

@Transactional
public void testExceptionNotMatch() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原記錄:" + user);
    update(id);
    throw new Exception("事務不生效:異常不匹配");
}

@Transactional 注解默認處理運行時異常,即只有拋出運行時異常時,才會觸發事務回滾,否則并不會回滾,至于深層次的原理,源碼部分會給你解讀。

4. 多線程

父線程拋出異常

父線程拋出異常,子線程不拋出異常:

public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原記錄:" + user);
    update(id);
}
@Transactional(rollbackFor = Exception.class)
public void testMultThread() throws Exception {
    new Thread(new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            testSuccess();
        }
    }).start();
    throw new Exception("測試事務不生效");
}

父線程拋出線程,事務回滾,因為子線程是獨立存在,和父線程不在同一個事務中,所以子線程的修改并不會被回滾

子線程拋出異常

父線程不拋出異常,子線程拋出異常:

public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原記錄:" + user);
    update(id);
    throw new Exception("測試事務不生效");
}
@Transactional(rollbackFor = Exception.class)
public void testMultThread() throws Exception {
    new Thread(new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            testSuccess();
        }
    }).start();
}

由于子線程的異常不會被外部的線程捕獲,所以父線程不拋異常,事務回滾沒有生效。

源碼解讀

@Transactional 執行機制

我們只看最核心的邏輯,代碼中的 interceptorOrInterceptionAdvice 就是 TransactionInterceptor 的實例,入參是 this 對象。

紅色方框有一段注釋,大致翻譯為 “它是一個攔截器,所以我們只需調用即可:在構造此對象之前,將靜態地計算切入點。”

Spring事務@Transactional注解不生效怎么解決

this 是 ReflectiveMethodInvocation 對象,成員對象包含 UserController 類、testSuccess() 方法、入參和代理對象等。

Spring事務@Transactional注解不生效怎么解決

進入 invoke() 方法后:

Spring事務@Transactional注解不生效怎么解決

前方高能!!!這里就是事務的核心邏輯,包括判斷事務是否開啟、目標方法執行、事務回滾、事務提交。

Spring事務@Transactional注解不生效怎么解決

private 導致事務不生效原因

在上面這幅圖中,第一個紅框區域調用了方法 getTransactionAttribute(),主要是為了獲取 txAttr 變量,它是用于讀取 @Transactional 的配置,如果這個 txAttr = null,后面就不會走事務邏輯,我們看一下這個變量的含義:

Spring事務@Transactional注解不生效怎么解決

我們直接進入 getTransactionAttribute(),重點關注獲取事務配置的方法。

Spring事務@Transactional注解不生效怎么解決

前方高能!!!這里就是 private 導致事務不生效的原因所在,allowPublicMethodsOnly() 一直返回 false,所以重點只關注 isPublic() 方法。

Spring事務@Transactional注解不生效怎么解決

異常不匹配原因

我們繼續回到事務的核心邏輯,因為主方法拋出 Exception() 異常,進入事務回滾的邏輯:

Spring事務@Transactional注解不生效怎么解決

進入 rollbackOn() 方法,判斷該異常是否能進行回滾,這個需要判斷主方法拋出的 Exception() 異常,是否在 @Transactional 的配置中:

Spring事務@Transactional注解不生效怎么解決

我們進入 getDepth() 看一下異常規則匹配邏輯,因為我們對 @Transactional 配置了 rollbackFor = Exception.class,所以能匹配成功:

Spring事務@Transactional注解不生效怎么解決

示例中的 winner 不為 null,所以會跳過下面的環節。但是當 winner = null 時,也就是沒有設置 rollbackFor 屬性時,會走默認的異常捕獲方式。

Spring事務@Transactional注解不生效怎么解決

前方高能!!!這里就是異常不匹配原因的原因所在,我們看一下默認的異常捕獲方式:

Spring事務@Transactional注解不生效怎么解決

是不是豁然開朗,當沒有設置 rollbackFor 屬性時,默認只對 RuntimeException 和 Error 的異常執行回滾。

關于“Spring事務@Transactional注解不生效怎么解決”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

互助| 唐山市| 敦煌市| 屏东县| 呈贡县| 广德县| 麦盖提县| 康定县| 苗栗县| 乐都县| 镇赉县| 黎平县| 南靖县| 濮阳市| 修武县| 陆河县| 道真| 洪泽县| 新乡市| 明水县| 冷水江市| 祁东县| 庆云县| 繁昌县| 安新县| 东宁县| 宁武县| 民勤县| 永顺县| 安平县| 达州市| 宝山区| 广昌县| 隆昌县| 济宁市| 普兰店市| 岐山县| 宣恩县| 比如县| 永安市| 平果县|