4

为什么catch了异常,但事务还是回滚了?

 3 years ago
source link: https://blog.didispace.com/why-catch-exception-transaction-rollback/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前几天我发了这篇文章《我来出个题:这个事务会不会回滚?》

得到了很多不错的反馈,也有不少读者通过微信、群或者邮件的方式,给了我一些关于test4的回复。其中还有直接发给我测试案例,来证明我的答案是错的。

今天,我们就来一起看看test4这个争议很大的问题。如果您是刚打开这篇文章,不了解我们在讨论啥,那可以先点击查看之前的这篇《我来出个题:这个事务会不会回滚?》

通过这两篇文章的解析,相信你会对Spring Data JPA下的事务执行机制有质的飞跃。

为什么没回滚

先来说说,那些写了代码验证“不会回滚”的情况,把这些错误答案的原因先说清楚,然后再细说test4会回滚的情况。

根据这两天读者给我的案例或者描述清楚的一些情况,归结了一下,大家写的验证代码之所以不会回滚,主要有以下三个原因:

  1. 没有按照我题目开头说的,采用InnoDB存储引擎,用了MyISAM,不支持事务,自然不会复现。
  2. 没用按照我题目开头说的,采用JPA和JSR 303校验注解,比如:用了MyBaits,所以自然也不会复现。
  3. 定义事务的函数不是public类型,这个基础用法就不对了,事务本身就没生效

归结一下出现这些疑问的原因:没审题事务基础掌握不牢导致。关于事务基础使用的一些常见注意点,之前写过一篇文章,如果觉得这方面知识还不扎实的,建议读一读:《为什么加了@Transactional注解,事务没有回滚?》

为什么写了catch,还会回滚

先来看看执行时候报的异常:

javax.validation.ConstraintViolationException: Validation failed for classes [com.didispace.chapter310.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='个数必须在0和5之间', propertyPath=name, rootBeanClass=class com.didispace.chapter310.User, messageTemplate='{javax.validation.constraints.Size.message}'}
]
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:140) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:80) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.action.internal.EntityInsertAction.preInsert(EntityInsertAction.java:209) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:83) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:478) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:356) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1454) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:511) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3283) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2479) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:473) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:178) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:39) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:271) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:98) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]

这个异常是这个回滚的关键。这个异常javax.validation.ConstraintViolationException是哪里的呢?还记得以前说的JSR 303不?对的,是Bean Validation中的异常。

有的读者说这个不是RuntimeException,所以不会回滚。很显然,这类判断的都没有实际尝试一下,只要点开源码可以马上发现,这个异常就是属于RunTimeException的。

实际上,之所以会回滚,与这里使用Spring Data JPA以及Hibernate Validator有直接关系。从JPA 2.0开始,就默认支持了这些Bean Validation的实现,它提供了实体生命周期中pre-persist, pre-update,pre-remove三个事件发生时来执行校验的功能。而在校验的时候,当校验失败,抛出javax.validation.ConstraintViolationException时,当前事务就会被标记为rollback

要想了解,这其中到底发生了什么,跟踪源码是最好的方式。那么源码从哪里开始看呢?从异常日志中找线索吧。

从异常栈中找到最近的一个错误,点开看看。

错误行数在532行tx.commit(),习惯性的加上断点,这样下一次进来的时候可以看看当前情况下的各种参数情况。

同时看到下面还有个catch,既然532行出错了,那这里肯定会进,所以也加个端点,到时候可以进去看看。

执行程序,调用一下test4,执行到532行,然后进入下一步,看看会到哪里?

这个时候,会进入到org.hibernate.engine.transaction.internal.TransactionImpl,具体位置如下:

还是习惯性的,在下面两行重要位置加上断点,以便下次可以快速到这里。

继续按上看的步骤尝试下去,可以来到下图的位置:

可以看到校验异常是从271行出来的,结合278行和280行,是不是清楚这里回滚的原因了呢?

当我把上一篇问题推到很多地方之后,其实还是收到了不少负面的反馈,甚至还有说我误导读者,顺便问候了下我的祖宗。这些我就不跟爱喷的读者互杠了,我是一直都推崇碰到问题,尽量多深挖一些的学习方式。虽然有的时候出现问题,确实是由于不恰当的写法导致,但如果你没有理解这个错误的原因,本质还是对其底层逻辑不够了解。如果你能从问题中找到线索,并顺藤摸瓜地探究和思考,你能够收获到的东西,远比喷我来的强。

实践出真知,当你觉得困惑的时候,不如动手写一写,调一调,很多答案就能自然浮现!如果对于test4会回滚还不够理解的读者,那就跟着我上面的步骤,一步步尝试一下,可以观察的更深入一些,你对这部分逻辑的理解就更全面了。我们正在组建高质量的Spring技术交流群,欢迎各种热爱技术的开发者加入参与讨论。这里的每个人都有自己的闪光点,互相学习,取长补短,长期坚持,愿大家都会成为自己领域里的佼佼者!

原创不易,如果您觉得本文不错,欢迎转发到朋友圈,支持一下我的努力吧!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK