spring 事務管理失效緣由

      這幾天在作項目的時候遇到了spring事務管理引起的問題,同一段代碼通過了不一樣的服務層調用,缺出現了不同的結果,最後發現是spring事務管理引起的問題。在網上搜了一篇博客,裏面對spring的事務機制作了解釋,頓悟啊!!!留下此博客但願和你們分享。原文以下:java

Spring事務管理失效的緣由

2016年01月11日 3:06 PMspring

我的認爲, spring的聲明式事務是spring讓人感受用的最爽的功能之一. 但是在有些時候, 咱們使用spring的聲明式事務時卻並無效果. 是spring的問題嗎? 下面咱們先大體說明一下spring聲明式事務的原理, 而後再分析在什麼狀況下, spring的聲明式事務會失效.多線程

代理模式

咱們知道, spring的聲明式事務是基於代理模式的. 那麼說事務以前咱們仍是大體的介紹一下代理模式吧. 其實代理模式至關簡單, 就是將另外一個類包裹在咱們的類外面, 在調用咱們建立的方法以前, 先通過外面的方法, 進行一些處理, 返回以前, 再進行一些操做. 好比:測試

1
2
3
4
5
6
7
public class UserService{
    ...
    public User getUserByName(String name) {
       return userDao.getUserByName(name);
    }
    ...
}

那麼若是配置了事務, 就至關於又建立了一個類:spa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class UserServiceProxy extends UserService{
    private UserService userService;
    ...
    public User getUserByName(String name){
        User user = null;
        try{
            // 在這裏開啓事務
            user = userService.getUserByName(name);
            // 在這裏提交事務
        }
        catch(Exception e){
            // 在這裏回滾事務

            // 這塊應該須要向外拋異常, 不然咱們就沒法獲取異常信息了. 
            // 至於方法聲明沒有添加異常聲明, 是由於覆寫方法, 異常必須和父類聲明的異常"兼容". 
            // 這塊應該是利用的java虛擬機並不區分普通異常和運行時異常的特色.
            throw e;
        }
        return user;
    }
    ...
}

而後咱們使用的是UserServiceProxy類, 因此就能夠"免費"獲得事務的支持:線程

1
2
3
4
5
6
7
@Autowired
private UserService userService;    // 這裏spring注入的其實是UserServiceProxy的對象

private void test(){
    // 因爲userService是UserServiceProxy的對象, 因此擁有了事務管理的能力
    userService.getUserByName("aa");
}

哪些狀況下spring的事務管理會失效

private方法, final方法 和 static方法不能添加事務

上面的東西並不難. 那麼咱們能夠從上面知道些什麼呢? 首先, 因爲java繼承時, 不能重寫privatefinalstatic修飾的方法. 因此, 全部的private方法, final方法 和 static方法 都沒法直接添加spring的事務管理功能. 好比下面的代碼(完整代碼點擊這裏下載):代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * 保存兩個user對象. 添加了spring事務註解
 */
@Transactional
public final void saveErrorFinal(User user1, User user2) {
    UserDao userDao = getUserDao(); // 此處須要使用getUserDao方法. 不能直接使用userDao
    userDao.save(user1);
    System.out.println(10 / 0); // 引起異常
    userDao.save(user2);
}

/**
 * 靜態方法. 添加了spring事務註解
 */
@Transactional
public static void saveErrorStatic(UserDao userDao, User user1, User user2) {
    userDao.save(user1);
    System.out.println(10 / 0); // 引起異常
    userDao.save(user2);
}

/**
 * 私有方法保存方法. 添加了spring事務註解
 */
@Transactional
private void saveErrorPrivate(User user1, User user2) {
    userDao.save(user1);
    System.out.println(10 / 0); // 引起異常
    userDao.save(user2);
}

測試代碼以下:code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
 * 檢驗user1是否插入成功, 並嘗試刪除數據. 若是沒有插入, 則報錯, 並退出.
 */
private void validateInsertSuccess() {
    User user = userService.getUserByName(user1.getName());
    userService.deleteByName(user1.getName()); // 刪除用戶數據
    assertNotEquals("插入失敗!", -1, user.getId().intValue());
}

/**
 * 檢驗user1是否插入失敗. 若是插入成功, 則刪除數據, 並報錯.
 */
private void validateInsertFail() {
    User user = userService.getUserByName(user1.getName());
    userService.deleteByName(user1.getName()); // 刪除用戶數據
    assertEquals("插入成功, 事務沒有生效!" + user, -1, user.getId().intValue());
}

@Test
public void testSaveErrorFinal() {
    try {
        userService.saveErrorFinal(user1, user2);
    }
    catch (ArithmeticException e) {}    // 除零異常, 直接忽略
    validateInsertFail(); // 咱們假設有事務支持, user1添加失敗
}

@Test
public void testSaveErrorStatic() {
    try {
        UserService.saveErrorStatic(userDao, user1, user2);
    }
    catch (ArithmeticException e) {}
    validateInsertFail(); // 咱們假設有事務支持, user1添加失敗
}

因爲saveErrorPrivate方法外面是沒法調用的, 就暫時不去討論了. 咱們直接看testSaveErrorFinaltestSaveErrorStatic方法的運行結果:
testSaveErrorFinal的運行結果 testSaveErrorStatic的運行結果
很明顯, 事務並無生效. 也就是說private方法, final方法 和 static方法都沒有事務支持.對象

沒有經過代理對象調用添加事務的方法

仔細看看代理模式中的代碼, 就會發現不經過代理對象調用方法也會致使spring事務管理失效. 繞過代理對象最直接的方法就是本身new一個對象, 雖然這種可能性很是小:繼承

1
new UserService().save(user);

固然, 前面也說了, 這種可能性很是小. 那麼咱們看看第二種狀況, 這種狀況的可能性也不大:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * 保存兩個user對象, 中間產生異常. 驗證spring的事務是否能夠正常工做
 */
@Transactional
public void saveError(User user1, User user2) {
    userDao.save(user1);
    System.out.println(10 / 0); // 引起異常
    userDao.save(user2);
}

/**
 * 經過{@link #saveError(User, User)}保存數據, 該方法自己並無添加事務註解. 
 * 而是經過{@link #saveError(User, User)}方法使用事務
 */
public void saveByCallMethod(User user1, User user2) {
    //saveErrorPrivate(user1, user2);  // 或者調用saveErrorPrivate方法
    saveError(user1, user2);
}

因爲測試的代碼基本上和上面同樣, 因此這裏咱們就不貼測試的代碼了. 再說一次, 點擊這裏下載完整代碼). 實際上, 上面的saveByCallMethod方法仍是沒法得到spring的事務支持. 由於它的調用堆棧以下圖所示(從下向上):
saveByCallMethod的調用堆棧
最終結果就是spring的事務管理沒有生效. 這是或許你會想了, 那爲啥不直接給saveByCallMethod方法添加事務支持呢? 對啊, 因此我說這種狀況的可能性也不大. 下面咱們再看看事務管理和多線程纏在一塊兒時的狀況:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 經過建立一個新的線程, 調用{@link #saveError(User, User)}方法來保存用戶, 該方法和
 * {@link #saveError(User, User)}方法都添加了事務註解
 */
@Transactional
public void saveByThread(User user1, User user2) {
    new Thread(() -> {
        try {
            Thread.sleep(1000); //耗時操做
            saveError(user1, user2);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("保存完成");
    }).start();
}

測試代碼請參見我提供的完整代碼. 這樣的代碼已經有可能了吧? 那麼事務管理會生效嗎? 咱們再看看調用堆棧就知道了. saveByThread的調用堆棧
結果和咱們想的同樣, spring的事務管理並無生效.

總結

好了, 如今咱們來回顧一下, 在那些狀況下spring的事務管理會失效:

  • private方法沒法添加事務管理.
  • final方法沒法添加事務管理.
  • static方法沒法添加事務管理.
  • 當繞過代理對象, 直接調用添加事務管理的方法時, 事務管理將沒法生效.
相關文章
相關標籤/搜索