资讯专栏INFORMATION COLUMN

Spring 踩坑之@Transactional 神奇失效

derek_334892 / 2565人阅读

摘要:引言对于追求数据强一致性的系统,事务扮演者十分重要的角色最近在项目中遇到一个事务失效的问题,在此分享给大家。情景回放问题分析初步分析这是事务获取锁超时导致的错误,奇怪的是抛出异常但是事务没有回滚。唯一的解释是事务失效了。

引言

对于追求数据强一致性的系统,事务扮演者十分重要的角色.最近在项目中遇到一个事务失效的问题,在此分享给大家。

情景回放
### 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 加于接口方法后, 被它类的私有方法调用后, 有效

Transactional是否生效, 仅取决于是否加载于接口方法, 并且是否通过接口方法调用(而不是本类调用)。

如果大家有更好的方法,欢迎加入讨论!

参考博客:https://www.cnblogs.com/milto...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69240.html

相关文章

  • C3的坑之inline-block

    最近开始复习css一直在踩坑,今天分享一个inline-block 关于inline-block可能很多人都不熟悉,布局这方面很多人用的都是flex或者浮动,flex很强大毋庸置疑的可是关于兼容性就不是很让人满意,而浮动虽然很兼容可是觉得清除浮动就很麻烦,于此我在一些大型网站,例如我们的segmentfault的首页导航展示用的布局就是inline-block,觉得inline-block可以撸一...

    luck 评论0 收藏0
  • C3的坑之inline-block

    最近开始复习css一直在踩坑,今天分享一个inline-block 关于inline-block可能很多人都不熟悉,布局这方面很多人用的都是flex或者浮动,flex很强大毋庸置疑的可是关于兼容性就不是很让人满意,而浮动虽然很兼容可是觉得清除浮动就很麻烦,于此我在一些大型网站,例如我们的segmentfault的首页导航展示用的布局就是inline-block,觉得inline-block可以撸一...

    zengdongbao 评论0 收藏0
  • 小程序坑之获取不到e.target.dataset的值

    摘要:在页面与传值中我们经常用到的方式,然后通过取的值今天在获取值时怎么也取不到,后来发现对象有和属性,而就在中,所以通过取到了正确的值。另外最好不要用驼峰命名如,这样有时候也取不到值。 在页面与js传值中我们经常用到data-id=1的方式,然后通过e.target.dataset.id取id的值今天在获取值时怎么也取不到,后来发现e对象有currentTarget和target属性,而d...

    韩冰 评论0 收藏0
  • 小程序坑之showModal:fail parameter error

    摘要:提示原因是对象中的值应该为字符串正确的赋值打开方式应该是不得不说小程序中的坑真多,赋值类型还得是字符串 console提示:showModal:fail parameter error: parameter.content should be String instead of Undefined;原因是 Page({ data: { userInfo: {}, h...

    biaoxiaoduan 评论0 收藏0
  • 小程序坑之获取不到e.target.dataset的值

    摘要:在页面与传值中我们经常用到的方式,然后通过取的值今天在获取值时怎么也取不到,后来发现对象有和属性,而就在中,所以通过取到了正确的值。另外最好不要用驼峰命名如,这样有时候也取不到值。 在页面与js传值中我们经常用到data-id=1的方式,然后通过e.target.dataset.id取id的值今天在获取值时怎么也取不到,后来发现e对象有currentTarget和target属性,而d...

    Turbo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<