Spring事务
2、【源码】Spring Data JPA原理解析之事务注册原理
3、【源码】Spring Data JPA原理解析之事务执行原理
6、【源码】Spring事务之事务失效及原理
在前面的Spring事务序列博文中分享了Spring事务注册、事务执行原理、编程式事务使用及原理。然后,如果使用不当,依然会导致事务失效。本篇分享一下常见的Spring事务失效的场景,并分析失败的原因。
Java的访问权限主要有四种:private、default(包级访问权限)、protected、public。它们用于控制类、方法和变量的访问级别,限定了对应成员的可见性。
如果在非public的方法中添加@Transactional注解事务,则事务会失效。
如以下代码:
- @Service
- public class MemberStatisticsService {
-
- @Resource
- private MemberStatisticsRepository memberStatisticsRepository;
-
- @Transactional
- private int addStatistics(MemberStatisticsEntity entity) {
- // 省略其他
- int rs = memberStatisticsRepository.save(entity);
- return rs ? 1 : 0;
- }
-
- }
在addStatistics()方法被调用的时候,会执行TransactionAspectSupport的invokeWithinTransaction(),在该方法中,会调用TransactionAttributeSource的getTransactionAttribute()方法,获取TransactionAttribute对象。该方法会调用AbstractFallbackTransactionAttributeSource的computeTransactionAttribute()方法,从而调用具体的事务解析器,获得TransactionAttribute对象。
AbstractFallbackTransactionAttributeSource的computeTransactionAttribute()方法的源码如下:
- public abstract class AbstractFallbackTransactionAttributeSource
- implements TransactionAttributeSource, EmbeddedValueResolverAware {
-
- /**
- * 从方法的目标方法、目标类或原方法、原方法的类中查找Transaction的属性信息,没有找到返回null
- */
- @Nullable
- protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class> targetClass) {
- // 如果只允许公共方法才能拥有事务,则进行public判断
- if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
- return null;
- }
-
- // 找到具体的方法。如果当前方法是一个接口方法,需要找到目标类中的实现。如果targetClass为null,那么该方法不会改变
- Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
- // 查找方法的Transaction属性,抽象方法,由子类实现
- TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
- if (txAttr != null) {
- return txAttr;
- }
-
- // Transaction属性可能配置在目标类中
- txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
- if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
- return txAttr;
- }
-
- // 如果找到的目标方法和当前方法不同,即当前方法为接口方法或被重写的方法
- if (specificMethod != method) {
- // 再次从原方法中查找Transaction属性信息
- txAttr = findTransactionAttribute(method);
- if (txAttr != null) {
- return txAttr;
- }
- // 如果还没有找到,从原方法的定义类中查找Transaction属性信息
- txAttr = findTransactionAttribute(method.getDeclaringClass());
- if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
- return txAttr;
- }
- }
-
- return null;
- }
-
- }
在computeTransactionAttribute()方法中,会先判断对应的方法是否为public,如果不是,直接返回null,在TransactionAspectSupport.createTransactionIfNecessary()方法中就不会开启事务。
源码如下:
- public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
-
- /**
- * 如有必要,根据给定的TransactionAttribute创建事务
- */
- @SuppressWarnings("serial")
- protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
- @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
-
- // If no name specified, apply method identification as transaction name.
- // 如果未指定名称,则将方法标识应用为事务名称
- if (txAttr != null && txAttr.getName() == null) {
- txAttr = new DelegatingTransactionAttribute(txAttr) {
- @Override
- public String getName() {
- return joinpointIdentification;
- }
- };
- }
-
- TransactionStatus status = null;
- if (txAttr != null) {
- if (tm != null) {
- // 开启事务
- status = tm.getTransaction(txAttr);
- }
- else {
- if (logger.isDebugEnabled()) {
- logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
- "] because no transaction manager has been configured");
- }
- }
- }
- // 准备TransactionInfo对象,并返回
- return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
- }
- }
详细见:
所以,如果要是@Transactional事务生效,方法必须定义为public访问权限。
在项目中,为了规范编程,使用了自定义异常,如果使用不当,也容易导致事务失效。
1)首先自定义的异常不能直接继承Exception,因为事务默认只处理RuntineException或Error异常;
2)通过@Transactional的rollbackFor参数设置回滚异常时,指定了自定义的异常;
- @Slf4j
- @Service
- public class GoodsService {
-
- @Resource
- private GoodsRepository goodsRepository;
-
- @Resource
- private GoodsDetailRepository goodsDetailRepository;
-
- @Transactional(rollbackFor = BusinessException.class)
- public GoodsEntity save(GoodsEntity entity, GoodsDetailEntity detail) {
- entity.setCreateTime(new Date());
- try {
- entity = goodsRepository.save(entity);
- detail.setId(entity.getId());
- detail = goodsDetailRepository.save(detail);
- entity.setDetail(detail);
- } catch (Exception e) {
- throw new BusinessException(e.getMessage());
- }
- // 省略其他
- return entity;
- }
- }
以上代码,如果在try-catch以外报了别的异常,那么会导致事务失效。
TransactionAspectSupport的相关源码如下:
- public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
- /**
- * 执行回滚
- */
- protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
- if (txInfo != null && txInfo.getTransactionStatus() != null) {
- if (logger.isTraceEnabled()) {
- logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
- "] after exception: " + ex);
- }
- // 如果满足回滚规则
- if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
- try {
- // 进行事务回滚
- txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
- }
- catch (TransactionSystemException ex2) {
- logger.error("Application exception overridden by rollback exception", ex);
- ex2.initApplicationException(ex);
- throw ex2;
- }
- catch (RuntimeException | Error ex2) {
- logger.error("Application exception overridden by rollback exception", ex);
- throw ex2;
- }
- }
- else {
- // We don't roll back on this exception.
- // Will still roll back if TransactionStatus.isRollbackOnly() is true.
- try {
- // 如果TransactionStatus.isRollbackOnly()为true,则仍将回滚
- txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
- }
- catch (TransactionSystemException ex2) {
- logger.error("Application exception overridden by commit exception", ex);
- ex2.initApplicationException(ex);
- throw ex2;
- }
- catch (RuntimeException | Error ex2) {
- logger.error("Application exception overridden by commit exception", ex);
- throw ex2;
- }
- }
- }
- }
- }
当异常导致事务回滚时,要先通过TransactionAttribute.rollbackOn()判断对应异常是否满足回滚规则,如果不满足,依然会提交事务。rollbackOn()是接口方法,实现如下:
对于@Transactional注解,使用的是RuleBasedTransactionAttribute,该类继承DefaultTransactionAttribute。
对应源码如下:
- public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute implements Serializable {
- /**
- * 重写父类的方法,重写定义了是否需要回滚
- * @param ex
- * @return
- */
- @Override
- public boolean rollbackOn(Throwable ex) {
- if (logger.isTraceEnabled()) {
- logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
- }
-
- RollbackRuleAttribute winner = null;
- // deepest为最大值,相当于不管异常有多深,只要规则中有此异常都应该回滚
- int deepest = Integer.MAX_VALUE;
-
- // 如果有回滚规则,从列表中查找是否有能够匹配该异常的规则
- if (this.rollbackRules != null) {
- for (RollbackRuleAttribute rule : this.rollbackRules) {
- int depth = rule.getDepth(ex);
- if (depth >= 0 && depth < deepest) {
- deepest = depth;
- winner = rule;
- }
- }
- }
-
- if (logger.isTraceEnabled()) {
- logger.trace("Winning rollback rule is: " + winner);
- }
-
- // User superclass behavior (rollback on unchecked) if no rule matches.
- // 如果没有找到匹配的回滚规则,则返回父类的执行结果,即只回滚RuntimeException 或者 Error(比如OOM这种)
- if (winner == null) {
- logger.trace("No relevant rollback rule found: applying default rules");
- return super.rollbackOn(ex);
- }
- // 如果有匹配的回滚规则,则属于不需要回滚的,则返回false,即不回滚
- return !(winner instanceof NoRollbackRuleAttribute);
- }
- }
DefaultTransactionAttribute.rollbackOn()源码如下:
- public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute {
- /**
- * 是否回滚。只回滚RuntimeException 或者 Error(比如OOM这种)
- */
- @Override
- public boolean rollbackOn(Throwable ex) {
- return (ex instanceof RuntimeException || ex instanceof Error);
- }
- }
在RuleBasedTransactionAttribute.rollbackOn()方法中,会先判断异常信息是否在@Transactional中指定,如果没有,调用父类的rollbackOn()进行判断,父类DefaultTransactionAttribute的rollbackOn()方法,判断异常是否属于RuntimeException或Error,如果是,才会回滚事务。
不管是通过@Transactional注解实现事务还是编程式事务,都是在业务逻辑出现异常时,事务处理会捕获异常,并判断是否要进行事务回滚,然后抛出对应异常。如果业务异常自己捕获了,没有往外抛,也会导致事务失效。
- @Slf4j
- @Service
- public class GoodsService {
-
- @Resource
- private GoodsRepository goodsRepository;
-
- @Resource
- private GoodsDetailRepository goodsDetailRepository;
-
- @Transactional
- public GoodsEntity save(GoodsEntity entity, GoodsDetailEntity detail) {
- entity.setCreateTime(new Date());
- try {
- entity = goodsRepository.save(entity);
- detail.setId(entity.getId());
- detail = goodsDetailRepository.save(detail);
- entity.setDetail(detail);
- } catch (Exception e) {
- log.error(e.getMessage());
- return null;
- }
- return entity;
- }
- }
- @Slf4j
- @Service
- public class GoodsService {
-
- @Resource
- private GoodsRepository goodsRepository;
-
- @Resource
- private GoodsDetailRepository goodsDetailRepository;
-
- @Transactional
- public GoodsEntity save(GoodsEntity entity, GoodsDetailEntity detail) {
- entity.setCreateTime(new Date());
- try {
- entity = goodsRepository.save(entity);
- detail.setId(entity.getId());
- detail = goodsDetailRepository.save(detail);
- entity.setDetail(detail);
- } catch (Exception e) {
- log.error(e.getMessage());
- // 设置事务回滚
- TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
- return null;
- }
- return entity;
- }
- }
只需要在异常捕获的地方加上如下代码:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
事务在提交之前,会再做一次是否回滚判断,源码如下:
- package org.springframework.transaction.support;
-
- @SuppressWarnings("serial")
- public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
-
- @Override
- public final void commit(TransactionStatus status) throws TransactionException {
- if (status.isCompleted()) {
- throw new IllegalTransactionStateException(
- "Transaction is already completed - do not call commit or rollback more than once per transaction");
- }
-
- DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
- // 如果本地代码设置了回滚
- if (defStatus.isLocalRollbackOnly()) {
- if (defStatus.isDebug()) {
- logger.debug("Transactional code has requested rollback");
- }
- processRollback(defStatus, false);
- return;
- }
-
- // 全局事务被标记为仅回滚,但事务代码请求提交
- if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
- if (defStatus.isDebug()) {
- logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
- }
- processRollback(defStatus, true);
- return;
- }
- // 提交处理
- processCommit(defStatus);
- }
-
- }
在commit()方法中,在真正提交处理前,会先进行两个判断:
1)defStatus.isLocalRollbackOnly()如果返回true,会执行回滚;
2)判断!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly(),如果为true,也会执行回滚;
- @Service
- public class MemberStatisticsService {
-
- @Resource
- private TransactionTemplate transactionTemplate;
-
- @Resource
- private MemberStatisticsRepository memberStatisticsRepository;
-
- public int addStatistics(MemberStatisticsEntity entity) {
- boolean rs = addStatisticsBs(entity);
- // 省略其他
- return rs ? 1 : 0;
- }
-
- @Transactional
- public int addStatisticsBs(MemberStatisticsEntity entity) {
- // 省略其他
- memberStatisticsRepository.save(entity);
- return true;
- }
-
- }
如果是通过addStatistics()方法,方法没有添加@Transactional注解,然后调用带@Transactional注解的addStatisticsBs()方法时,当addStatisticsBs()出现异常时,事务不会回滚。
在上面的博客中介绍了方法中添加@Transactional注解时,该类会生成代理类,代理类中添加了TransactionInterceptor拦截器,从而实现了事务管理。
当addStatistics()方法执行时,会先执行ReflectiveMethodInvocation.proceed()方法,循环遍历所有的拦截器。执行完所有拦截器之后,再执行动态代理对象的target类的对应方法,即原方法。详见:
【源码】Spring Data JPA原理解析之Repository执行过程及SimpleJpaRepository源码-CSDN博客
博客中的动态代理方法拦截部分。
因为addStatistics()没有添加@Transactional注解,所以执行target的addStatistics()方法,所以在addStatistics()方法内部的this对象是target,而不是代理对象。所以在addStatistics()内部调用addStatisticsBs()方法时,是执行target的addStatisticsBs()方法,所以不再先执行ReflectiveMethodInvocation.proceed(),也就不会执行TransactionInterceptor拦截器,所以没有开启事务管理。
针对内部方法调用,导致事务失效的问题有如下处理方案:
1)直接在addStatistics()方法添加@Transactional事务注解;
这种方式最为简单,但如果初衷是为了避免大事务,这种做法就没法解决大事务的问题;
2)使用编程式事务;
在addStatistics()方法中使用编程式事务,在事务里面调用addStatisticsBs()方法;
3)在类中添加自身属性;
在MemberStatisticsService中添加一个类型为MemberStatisticsService的属性memberStatisticsService,在addStatistics()方法中,使用memberStatisticsService.addStatisticsBs()调用。代码如下:
- @Service
- public class MemberStatisticsService {
-
- @Resource
- private TransactionTemplate transactionTemplate;
-
- @Resource
- private MemberStatisticsService memberStatisticsService;
-
- @Resource
- private MemberStatisticsRepository memberStatisticsRepository;
-
- public int addStatistics(MemberStatisticsEntity entity) {
- boolean rs = memberStatisticsService.addStatisticsBs(entity);
- // 省略其他
- return rs ? 1 : 0;
- }
-
- @Transactional
- public int addStatisticsBs(MemberStatisticsEntity entity) {
- // 省略其他
- memberStatisticsRepository.save(entity);
- return true;
- }
-
- }
事务传播特性的详细说明见
限于篇幅,本篇先分享到这里。
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。