參考:https://www.jianshu.com/p/f5fc14bde8a0java
後續測試代碼的完整項目:https://files.cnblogs.com/files/hellohello/demo2.rarspring
後續說的事務註解都是指 import javax.transaction.Transactional;事務註解若是修飾在類上,則等價與做用在這個類的全部方法上,若是僅修飾在函數上,則僅僅做用在這個函數上,對其餘函數沒有效果。數據庫
測試一、二、3點:網絡
服務類代碼,成員函數上存在事務註解,因此會生成代理對象函數
@Service public class ConfSystemService { // 用於記錄被代理對象的引用 public static ConfSystemService ins ; public ConfSystemService(){ ins = this; } @Transactional public void saveWithOk(int id){ // ... } }
測試類中進行測試單元測試
@Autowired ConfSystemService confSystemService; @Test public void test3(){ Assert.isTrue(!confSystemService.equals(ConfSystemService.ins),"判斷是否屬於同一個對象"); Assert.isTrue(confSystemService.getClass().getSuperclass().equals(ConfSystemService.class),"判斷是不是父類子類關係"); Assert.isTrue(!ConfSystemService.ins.getClass().getSuperclass().equals(ConfSystemService.class),"判斷是不是父類子類關係"); }
若是把服務類中的事務註解去掉,則不會生成代理對象,那上面例子中的靜態屬性保存的對象,跟其餘地方註冊的服務對象,就是同一個對象了。測試
測試四、5點:this
在服務類中添加以下代碼:spa
/** * dao. */ @Autowired ConfSystemRepository confSystemRepository; /** * 使用dao保存數據後,報錯. * @param id . */ @Transactional public void saveWithErr(int id){ ConfSystem confSystem = new ConfSystem(); confSystem.setConfigurename("123"); confSystem.setConfigurevalue("456"); confSystem.setConfsystemid(id); confSystemRepository.save(confSystem); throw new RuntimeException("模擬錯誤"); } /** * 正常保存數據. * @param id . */ @Transactional public void saveWithOk(int id){ ConfSystem confSystem = new ConfSystem(); confSystem.setConfigurename("123"); confSystem.setConfigurevalue("456"); confSystem.setConfsystemid(id); confSystemRepository.save(confSystem); } /** * 判斷數據是否成功插入到了數據庫中. * @param id . * @return . */ public boolean checkExist(int id){ return confSystemRepository.findById(id).isPresent(); }
執行單元測試代理
@Test public void test1() { try{ confSystemService.saveWithErr(888); }catch (Exception e){ e.printStackTrace(); } Assert.isTrue(!confSystemService.checkExist(888)); } @Test public void test2() { confSystemService.saveWithOk(999); Assert.isTrue(confSystemService.checkExist(999)); }
第一個單元測試中,模擬了一個報錯,事務就回滾了,因此檢查出數據不存在。其中實際調用了代理類的saveWithErr方法,而這個方法中調用了被代理對象的saveWithErr方法,而這個方法中拋出了一個RuntimeException,這個異常被代理類的saveWithErr方法檢測到,並且代理類發現當前方法處於事務註解的做用下,因此代理類就會將事務回滾,最後再將這個RuntimeException拋出去,讓調用者知道這個方法報錯了。
以上提到了回滾的兩個必要條件:
測試第1條,場景:服務類中未出於事務註解做用下的方法調用了,處於事務註解做用下的方法。服務類中添加以下代碼:
public void funcWithNoTrans(int id){ saveWithErr(id); }
測試類中添加以下測試用例(和以前的test1很相似,至不要調用的服務方法不同):
@Test public void test5() { try{ confSystemService.funcWithNoTrans(888); }catch (Exception e){ e.printStackTrace(); } Assert.isTrue(!confSystemService.checkExist(888)); }
這個測試用例沒法經過。是由於代理類執行當前方法時,雖然執行的被代理對象的方法中拋出了異常,可是代理類發現當前方法(funcWithNoTrans)並非處於事務註解做用下,因此事務沒有回滾。
測試第2條,場景:沒有拋出uncheck exception,而是拋出了自定義的異常,同時沒有作對應配置,在服務類中添加代碼:
// 自定義異常 public static class MyException extends Exception { // ... } // 沒有配置 @Transactional public void saveWithCustomException(int id) throws MyException { ConfSystem confSystem = new ConfSystem(); confSystem.setConfigurename("123"); confSystem.setConfigurevalue("456"); confSystem.setConfsystemid(id); confSystemRepository.save(confSystem); throw new MyException(); } // 有配置 @Transactional(rollbackOn = MyException.class) public void saveWithCustomCfgException(int id) throws MyException { ConfSystem confSystem = new ConfSystem(); confSystem.setConfigurename("123"); confSystem.setConfigurevalue("456"); confSystem.setConfsystemid(id); confSystemRepository.save(confSystem); throw new MyException(); }
測試代碼:
@Test public void test6() { try{ confSystemService.saveWithCustomCfgException(111); }catch (Exception e){ e.printStackTrace(); } Assert.isTrue(!confSystemService.checkExist(111),"拋出已配置的自定義異常"); } @Test public void test7() { try{ confSystemService.saveWithCustomException(222); }catch (Exception e){ e.printStackTrace(); } Assert.isTrue(confSystemService.checkExist(222),"拋出未配置的自定義異常"); }
兩個測試都能經過。
還有另一個場景:服務類內部出現異常了,可是內部try...catch處理掉了,致使代理類檢測不出來被代理對象內部其實出現了異常,最終事務也沒有回滾。
ps:測試發現,換用另外一個spring包下的Transactional註解,具備以上相同的效果,只不過配置註解那裏,得改爲 rollbackFor。這兩個註解功能上其實沒啥區別,只是配置的屬性名有點差別而已。
其餘
被註解修飾的函數是運行在一個事務內,因此要保證這個函數運行的時間要儘量短(如不要穿插網絡請求,實在不行的話,則將網絡請求剝離到這個事務方法以外,這樣就不影響這個方法的執行時間了)。並且在這個函數內,該拋的運行時異常要拋出來,而不要trycatch住,不然致使事務沒法回滾
事務註解,加到service方法上,service方法內調用不一樣的dao來操做數據。一個sevice方法內有兩個數據庫修改操做以上,才須要在這個service方法上加事務註解