這幾天在作項目的時候遇到了spring事務管理引起的問題,同一段代碼通過了不一樣的服務層調用,缺出現了不同的結果,最後發現是spring事務管理引起的問題。在網上搜了一篇博客,裏面對spring的事務機制作了解釋,頓悟啊!!!留下此博客但願和你們分享。原文以下:java
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"); } |
private
方法, final
方法 和 static
方法不能添加事務上面的東西並不難. 那麼咱們能夠從上面知道些什麼呢? 首先, 因爲java繼承時, 不能重寫private
, final
, static
修飾的方法. 因此, 全部的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
方法外面是沒法調用的, 就暫時不去討論了. 咱們直接看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的事務支持. 由於它的調用堆棧以下圖所示(從下向上):
最終結果就是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(); } |
測試代碼請參見我提供的完整代碼. 這樣的代碼已經有可能了吧? 那麼事務管理會生效嗎? 咱們再看看調用堆棧就知道了.
結果和咱們想的同樣, spring的事務管理並無生效.
好了, 如今咱們來回顧一下, 在那些狀況下spring的事務管理會失效:
private
方法沒法添加事務管理.final
方法沒法添加事務管理.static
方法沒法添加事務管理.