關於spring事務註解實戰
1.概述
spring的事務註解@Transaction 相信很多人都用過,而@Transaction 默認配置適合80%的配置。
本篇文章不是對spring註解事務做詳細介紹,而是解決一些實際場景下遇到的問題
2.事務回滾
2.1 默認回滾策略
@Transactional public void rollback() throws SQLException { // update db throw new SQLException("exception"); }
上述代碼事務會回滾嗎?不會的,就算拋出SQLException了,但是之前的數據庫操作依然會提交,原因就是@Transactional默認情況下只回滾RuntimeException和Error。
2.2 指定回滾異常
因此,如果要指定哪些異常需要回滾,則通過配置@Transactional中rollbackFor,例如
@Transactional(rollbackFor = {SQLException.class}) public void rollback() throws SQLException { // update db throw new SQLException("exception"); }
按照上面例子,那指定的SQLException,當拋出RuntimeException的時候,還會回滾嗎?
@Transactional(rollbackFor = {SQLException.class}) public void rollback() throws SQLException { // update db throw new Runtime("exception"); }
還是會回滾的。
2.3 事務嵌套的回滾
假設有下面的邏輯,事務會回滾嗎(或者說 updateA,updateB,updateC)哪些更新會提交
@Transactional public void rollback() { // updateA try{ innelTransaction() }catch(RuntimeException e){ //do nothing } //updateC } @Transactional public void innelTransaction() throws SQLException { // updateB throw new RuntimeException("exception"); }
答案是都會提交,因為事務的回滾的匹配規則是嵌套最外層的為準 那如果換成下面的代碼
@Transactional public void rollback() throws SQLException{ // updateA innelTransaction() //updateC } @Transactional(rollbackFor = SQLException.class) public void innelTransaction() throws SQLException { // updateB throw new SQLException("exception"); }
updateA和updateB依然會提交,updateC不會提交(因為代碼執行不到這裏)
2.4 小結
所以,在需要事務回滾的時候,最好還是拋出RuntimeException,並且不要在代碼中捕獲此類異常
三、事務傳播性
@Transaction中的propagation的可以配置事務的傳播性,網上介紹的很多,就直接復制一段
PROPAGATION_REQUIRED--支持當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。 (也是默認策略)
PROPAGATION_SUPPORTS--支持當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY--支持當前事務,如果當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW--新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED--以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER--以非事務方式執行,如果當前存在事務,則拋出異常。
3.1 如何在事務中讀取最新配置
有時候需要在一個事務中,讀取最新數據(默認是讀取事務開始前的快照)。其實很簡單,只要使用上面PROPAGATION_NOT_SUPPORTED傳播性就可以了。
@Transactional public void transaction() throws SQLException { // do something queryNewValue(); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void queryNewValue() throws SQLException { //查詢數據中的最新值 }
你以為上面的能生效,就錯了,在同一個類中調用事務方法,不能直接簡單調用。
四、內部調用事務方法
事務註解的實質就是在創建一個動態代理,在調用事務方法前開啟事務,在事務方法結束以後決定是事務提交還是回滾。
因此,直接在類內部中調用事務方法,是不會經過動態代理的
。 因此,如果要使方法B點事務生效,必須這樣
4.1 解決辦法
解決思路:需要在內部調用方法B的時候,找到當前類的代理類,用代理類去調用方法B
4.1.1 解決辦法1
@Service public class MyService{ @Transactional public void transaction(){ // do something ((MyService) AopContext.currentProxy()).queryNewValue(); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void queryNewValue(){ //查詢數據中的最新值 } }
通過AopContext.currentProxy()可以拿到當前類的代理類,但是要使用這個時候,必須在啟動類上加上
@EnableAspectJAutoProxy(exposeProxy=true)
4.1.2 解決辦法2
既然是要拿到當前代理類,那其實直接在Spring的容器裏面去拿也可以啊。在spring中拿Bean的方法大致有2種
通過註入
@Service public class MyService{ @Autowired private MyService self; @Transactional public void transaction() { // do something self.queryNewValue(); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void queryNewValue() { //查詢數據中的最新值 } }
tips:spring現在對一些循環依賴是提供支持的,簡單來說,滿足:
- Bean是單例
- 註入的方式不是構造函數註入
通過BeanFactory
@Service public class MyService implements BeanFactoryAware{ private MyService self; @Transactional public void transaction(){ // do something self.queryNewValue(); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void queryNewValue() { //查詢數據中的最新值 } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { self = beanFactory.getBean(MyService.class); } }
雖然在前一種方法在大部分情況下是可行的,但是有些情況下還是會報循環依賴的問題(可以嘗試在該類中即有@Transaction,又有@Async)。而使用BeanFactoryAware來註入,這樣是在Bean初始完成後再set進來,而不是依賴Spring來解決循環依賴
4.2 需要註意的地方
- 使用@Transaction註解的方法,必須用public來修飾。
- 其實不止是@Transaction,其他類似@Cacheable,@Retryable等依賴spring proxy也必須使用上述方式達到內部調用。
關於spring事務註解實戰