@Transactional失效問題

轉自:https://segmentfault.com/a/1190000014617571

### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
; SQL []; Lock wait timeout exceeded; try restarting transaction; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
    at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:259)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73)
    at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
    at com.sun.proxy.$Proxy121.update(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate.java:294)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:62)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)

問題分析

初步分析這是事務獲取鎖超時導致的錯誤,奇怪的是拋出異常但是事務沒有回滾。或許你們說MySQLTransactionRollbackException是檢查性異常(@Transactional默認只捕獲非檢查性異常),但是項目添加了註解:@Transactional(rollbackFor = Exception.class)。唯一的解釋是——事務失效了。

 ProductService.java
/**********************************************************************/
public interface ProductService{
    Integer getPrice(ProductInfo p);
    Integer compute(ProductInfo p);
}
/**********************************************************************/


ProductServiceImpl.java
/**********************************************************************/
@Service
public class ProductServiceImpl implements ProductService{

    public Integer getPrice(ProductInfo p){
        ...
        compute(p);
        ...
    }

    @Transactional(rollbackFor = Exception.class)
    public Integer compute(ProductInfo p){ //TestService的普通方法
        try{
            ...
        }catch(Exception e){
             e.printStackTrace();
             return -1;
        }
    }
}
/**********************************************************************/

初看這段代碼,沒啥毛病啊。噢,不對,compute 方法內部catch了異常,spring aop無法捕獲異常。如果需要捕獲異常,需要手動回滾,於是compute方法修改如下:

    @Transactional(rollbackFor = Exception.class)
        public Integer compute(ProductInfo p){ //TestService的普通方法
            try{
                ...
            }catch(Exception e){
                 e.printStackTrace();
                 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//手動回滾事務
                 return 0;
            }
        }

繼續運行,結果發現事務還是未生效。通過查詢資料發現,service方法直接調用了本類的一個方法(沒有通過接口調用),該方法上的事務將不會生效。

解決方案

想啓用本類的普通方法的事務,通過接口來調用該方法即可生效。如果先在方法內部catch異常,需要添加TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();否則可以在外面捕獲這個異常。下面是在方法內部捕獲異常的示例:

 ProductService.java
/**********************************************************************/
public interface ProductService{
    Integer getPrice(ProductInfo p);
    Integer compute(ProductInfo p);
}
/**********************************************************************/


ProductServiceImpl.java
/**********************************************************************/
@Service
public class ProductServiceImpl implements ProductService{
    @Autowired
    private ProductService productService;
    
    public Integer getPrice(ProductInfo p){
        productService.compute(p);
    }

    @Transactional(rollbackFor = Exception.class)
    public Integer compute(ProductInfo p){ 
        try{
            ...
        }catch(Exception e){
             e.printStackTrace();
             TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
             return 0;
        }
    }
}
/**********************************************************************/

總結

Spring Transactional一直是RD的事務神器,但是如果用不好,反會傷了自己。下面總結@Transactional經常遇到的幾個場景:

@Transactional 加於private方法, 無效
@Transactional 加於未加入接口的public方法, 再通過普通接口方法調用, 無效
@Transactional 加於接口方法, 無論下面調用的是private或public方法, 都有效
@Transactional 加於接口方法後, 被本類普通接口方法直接調用, 無效
@Transactional 加於接口方法後, 被本類普通接口方法通過接口調用, 有效
@Transactional 加於接口方法後, 被它類的接口方法調用, 有效
@Transactional 加於接口方法後, 被它類的私有方法調用後, 有效

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章