【Java系列002】正确使用@Transactional注解

[亡魂溺海] 提交于 2020-04-05 17:42:43

你好,我是miniluo,今天我和你聊聊Spring声明式事务不生效的坑。

下面就让我和你一起学习有哪些几种情况下Spring声明式事务不生效的坑。

没有正确理解@Transactional注解

你是否曾经写过和下文类似的代码?

 @Service("productService")public class ProductServiceImpl implements IProductService {@Overridepublic void createProduct(Product product) {//此处可能会实现一些业务逻辑代码,判断是否满足创建产品的条件//请求内部方法创建产品 product.setProductName("Spring声明式事务"); executeCreateProduct(product); } @Transactionalprivate void executeCreateProduct(Product product){if(Objects.isNull(product)){throw new RuntimeException("无法创建空产品"); } product.create();//模拟出现异常期待事务回滚int zero = 1/0; }}

经过测试,我们发现抛出了异常,可是产品还是被创建了。原因是,Spring 默认通过动态代理的方式实现 AOP,对目标方法进行增强,private 方法无法代理到,这么说我们修改为public方法应该有效果。改完后再尝试发现产品还是被创建。这里不得不说下@Transactional生效必须通过代理过的类从外部调用目标方法才能生效。知道这个原因后,我们把ProductServiceImpl以自身的方式注入,调整代码如下:

 @Service("productService")public class ProductServiceImpl implements IProductService {@Autowiredprivate ProductServiceImpl self;@Overridepublic void createProduct(Product product) {//此处可能会实现一些业务逻辑代码,判断是否满足创建产品的条件 product.setProductName("Spring声明式事务"); self.executeCreateProduct(product); //请求内部方法创建产品 } @Transactionalpublic void executeCreateProduct(Product product){if(Objects.isNull(product)){throw new RuntimeException("无法创建空产品"); } product.create();int zero = 1/0; //模拟出现异常期待事务回滚 }}

再次测试,我们发现产品并没有被创建,说明事务已生效,因为self 是由 Spring 通过 CGLIB 方式增强过的类。这种方式虽然能解决,但是并不建议这么做,而应该把@Transactional写在接口的实现方法上,建议把查询和更新(新增)分开。

异常处理不当导致事务失效

情况一:异常无法从方法传播出去,导致事务失效

 ​​​​​​@Service("productService")@Slf4jpublic class ProductServiceImpl implements IProductService {@Override@Transactionalpublic void createProduct(Product product) {//此处可能会实现一些业务逻辑代码,判断是否满足创建产品的条件 product.setProductName("Spring声明式事务");try { executeCreateProduct(product); //请求内部方法创建产品 }catch (Exception ex){ log.error("创建产品失败",ex); } } private void executeCreateProduct(Product product){if(Objects.isNull(product)){throw new RuntimeException("无法创建空产品"); } product.create();int zero = 1/0; //模拟出现异常期待事务回滚 }}

这种写法在实际编码中常犯的错误,若要使其生效,只需在catch中增加

TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();

情况二:受检异常导致事务不生效
​​​​​​

@Service("productService")
@Slf4j
public class ProductServiceImpl implements IProductService {
@Override
@Transactional
public void createProduct(Product product)
throws IOException {
//此处可能会实现一些业务逻辑代码,判断是否满足创建产品的条件
        product.setProductName("Spring声明式事务");
        executeCreateProduct(product);     //请求内部方法创建产品
    }

private void executeCreateProduct(Product product)
throws IOException {
if(Objects.isNull(product)){
throw new RuntimeException("无法创建空产品");
        }
        product.create();
        Files.readAllLines(Paths.get("transaction-not-rollback"));
    }
}

或许有这么个场景,这些代码并非你写的,你并不知道外层或里面的业务逻辑实现,改动可能会有影响存量功能;这个时候我们该怎么办呢?其实也不难,@Transactional已经为我们给出了解决方案,只需将@Transactional调整为

 @Transactional(rollbackFor = Exception.class)

至此,我和你一起回顾了平时编码过程中常见的坑,下面再一起来看看关于事务传播性的坑。

事务传播性

我们一起来看下面的两段代码,第一段是实现创建产品和产品上架。

 ​​​​​@Service("productService")@Slf4jpublic class ProductServiceImpl implements IProductService {@Autowiredprivate IOfferService offerService;/** * 期望:产品的创建并不会因上架失败导致回滚 * @param product */@Override@Transactionalpublic void createProduct(Product product){//此处可能会实现一些业务逻辑代码,判断是否满足创建产品的条件 product.setProductName("Spring声明式事务"); executeCreateProduct(product); //请求内部方法创建产品if(Status.UPSHELVE.equals(product.getStatus())){ offerService.productUpShelve(product);//创建产品后,产品随即上架 } } private void executeCreateProduct(Product product) {if(Objects.isNull(product)){throw new RuntimeException("无法创建空产品"); } product.create(); }}

第二段是产品上架的实现。

 ​​@Service("offerService")public class OfferServiceImpl implements IOfferService {@Override@Transactionalpublic void productUpShelve(Product product) { product.upShelve();throw new RuntimeException("上架失败"); }}

我们的期望是产品的创建成功与否不应该受到上架业务代码影响,我们测试后发现,产品上架失败,导致创建产品回滚;这是什么原因呢?这是因为创建产品的事务和产品上架的事务是同一个事务,所以上架失败自然会导致创建产品回滚。了解事务传播性的同学都知道,我们只需要在产品上架的方法配置事务的传播行为即可,告诉@Transactional,我是要自己独立事务

@Transactional(propagation = Propagation.REQUIRES_NEW)

好了,关于Spring声明式事务常踩的3个坑已经和你一起学习完,期待对你有所帮助和启发。

思考和讨论

1、文中我们通过注入自己的方式解决了事务不生效的情况,请问能否使用this来完成呢?this.executeCreateProduct(product); 

2、说到事务的传播行为,除了我所说的之外,还有哪些呢?

欢迎留言与我分享和指正!也欢迎你把这篇文章分享给你的朋友或同事,一起交流。

感谢您的阅读,我们下节再见!​​​​​​​

关注我们的公众号,不定时分享技术、管理、业务等不同领域的文章与您一起学习交流。也欢迎投稿:xiaouoron@foxmail.com

​​​​​​​

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!