共計 3488 個字符,預計需要花費 9 分鐘才能閱讀完成。
本篇內容介紹了“怎么解決數據庫事務居然沒生效問題”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓丸趣 TV 小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
Spring 聲明式事務提供給 Javaer 們方便的事務配置方式,再搭配 Spring Boot 自動配置,基本只需在方法上添加 @Transactional 注解,即可瞬間開啟方法的事務性配置。
但僅為方法添加 @Transactional 注解
你就以為這就夠了嗎?
事務未被正確處理,一般不會導致停止服務,更不易在測試階段復現。但隨系統業務越來越復雜,就會帶來大量數據不一致問題,隨后就是大量線上問題而后人工排查檢修數據。
1 你的 Spring 事務怎么才算生效?
使用 @Transactional 開啟聲明式事務時,靈魂發問:事務生效了嗎?
案例
用戶表實體類
DAO 層
根據 username 查詢所有數據
@Repository public interface UserRepository extends JpaRepository UserEntity, Long { List UserEntity findByName(String name); }
Service 層
UserService 類
負責業務邏輯處理,包括如下方法:
createUserWrong1 調用 private 方法:
createUserPrivate,被 @Transactional 注解。當傳入的用戶名包含 test 則拋異常,讓用戶的創建操作失敗,期望事務回滾:
getUserCount
Controller 層
調用一下剛才定義的 UserService 中的入口方法 createUserWrong1。
測試結果
即便用戶名不合法,用戶也能創建成功。刷新瀏覽器,多次發現非法用戶注冊。
2 @Transactional 怎么確保生效?
除非特殊配置 (比如使用 AspectJ 靜態織入實現 AOP),否則只有定義在 public 方法上的 @Transactional 才能生效。
Spring 默認通過動態代理實現 AOP,對目標方法增強,private 方法無法代理到,自然也無法動態增強事務處理邏輯。
那簡單,把 createUserPrivate 方法改為 public 即可。
在 UserService 中再建一個入口方法 createUserWrong2,來調用這個 public 方法再次嘗試:
public int createUserWrong2(String name) { try { this.createUserPublic(new UserEntity(name)); } catch (Exception ex) { log.error( create user failed because {} , ex.getMessage()); } return userRepository.findByName(name).size(); } // 標記了 @Transactional 的 public 方法 @Transactional public void createUserPublic(UserEntity entity) { userRepository.save(entity); if (entity.getName().contains(test)) throw new RuntimeException(invalid username! }
新的 createUserWrong2 方法事務同樣不生效。
必須通過代理過的類從外部調用目標方法
要調用增強過的方法必然是調用代理后的對象。
嘗試修改 UserService,注入一個 self,然后再通過 self 實例調用標記有 @Transactional 注解的 createUserPublic 方法。設置斷點可以看到,self 是由 Spring 通過 CGLIB 方式增強過的類。
CGLIB 通過繼承方式實現代理類,private 方法在子類不可見,自然也就無法進行事務增強;
this 指針代表對象自己,Spring 不可能注入 this,所以通過 this 訪問方法必然不是代理。
把 this 改為 self,在 Controller 中調用 createUserRight 方法可以驗證事務生效了:非法的用戶注冊操作可以回滾。
雖然在 UserService 內部注入自己調用自己的 createUserPublic 可以正確實現事務,但這不符合習慣用法。更合理的實現方式是,讓 Controller 直接調用之前定義的 UserService 的 createUserPublic 方法。
@GetMapping(right2) public int right2(@RequestParam( name) String name) { try { userService.createUserPublic(new UserEntity(name)); } catch (Exception ex) { log.error( create user failed because {} , ex.getMessage()); } return userService.getUserCount(name); }
this 自調用 /self 調用 /Controller 調用 UserService
this 自調用
無法走到 Spring 代理類
后兩種
調用的 Spring 注入的 UserService,通過代理調用才有機會對 createUserPublic 方法進行動態增強。
推薦在開發時打開相關 Debug 日志,以了解 Spring 事務實現的細節。
比如 JPA 數據庫訪問,可以這么開啟 Debug 日志:
logging.level.org.springframework.orm.jpa=DEBUG
開啟日志后再比較下在 UserService 中 this 調用、Controller 中通過注入的 UserService Bean 調用 createUserPublic 的區別。
很明顯,this 調用因沒走代理,事務沒有在 createUserPublic 生效,只在 Repository 的 save 生效:
// 在 UserService 中通過 this 調用 public 的 createUserPublic [23:04:30.748] [http-nio-45678-exec-5] [DEBUG] [o.s.orm.jpa.JpaTransactionManager:370 ] - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT [DEBUG] [o.s.orm.jpa.JpaTransactionManager :370 ] - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT // 在 Controller 中通過注入的 UserService Bean 調用 createUserPublic [10:10:47.750] [http-nio-45678-exec-6] [DEBUG] [o.s.orm.jpa.JpaTransactionManager :370 ] - Creating new transaction with name [org.geekbang.time.commonmistakes.transaction.demo1.UserService.createUserPublic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
這種實現在 Controller 里處理異常顯得繁瑣,還不如直接把 createUserWrong2 加 @Transactional 注解,然后在 Controller 中直接調用該方法。
“怎么解決數據庫事務居然沒生效問題”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注丸趣 TV 網站,丸趣 TV 小編將為大家輸出更多高質量的實用文章!