• 【源码】Spring事务之事务失效及原理


    Spring事务

    1、【源码】SpringBoot事务注册原理

    2、【源码】Spring Data JPA原理解析之事务注册原理

    3、【源码】Spring Data JPA原理解析之事务执行原理

    4、【源码】SpringBoot编程式事务使用及执行原理

    5、【源码】Spring事务之传播特性的详解

    6、【源码】Spring事务之事务失效及原理

    前言

    在前面的Spring事务序列博文中分享了Spring事务注册、事务执行原理、编程式事务使用及原理。然后,如果使用不当,依然会导致事务失效。本篇分享一下常见的Spring事务失效的场景,并分析失败的原因。

    访问权限问题

    Java的访问权限主要有四种:private、default(包级访问权限)、protected、public。它们用于控制类、方法和变量的访问级别,限定了对应成员的可见性。

    如果在非public的方法中添加@Transactional注解事务,则事务会失效。

    2.1 示例

    如以下代码:

    1. @Service
    2. public class MemberStatisticsService {
    3. @Resource
    4. private MemberStatisticsRepository memberStatisticsRepository;
    5. @Transactional
    6. private int addStatistics(MemberStatisticsEntity entity) {
    7. // 省略其他
    8. int rs = memberStatisticsRepository.save(entity);
    9. return rs ? 1 : 0;
    10. }
    11. }

    在addStatistics()方法被调用的时候,会执行TransactionAspectSupport的invokeWithinTransaction(),在该方法中,会调用TransactionAttributeSource的getTransactionAttribute()方法,获取TransactionAttribute对象。该方法会调用AbstractFallbackTransactionAttributeSource的computeTransactionAttribute()方法,从而调用具体的事务解析器,获得TransactionAttribute对象。

    2.2 原理

    AbstractFallbackTransactionAttributeSource的computeTransactionAttribute()方法的源码如下:

    1. public abstract class AbstractFallbackTransactionAttributeSource
    2. implements TransactionAttributeSource, EmbeddedValueResolverAware {
    3. /**
    4. * 从方法的目标方法、目标类或原方法、原方法的类中查找Transaction的属性信息,没有找到返回null
    5. */
    6. @Nullable
    7. protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class targetClass) {
    8. // 如果只允许公共方法才能拥有事务,则进行public判断
    9. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    10. return null;
    11. }
    12. // 找到具体的方法。如果当前方法是一个接口方法,需要找到目标类中的实现。如果targetClass为null,那么该方法不会改变
    13. Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
    14. // 查找方法的Transaction属性,抽象方法,由子类实现
    15. TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
    16. if (txAttr != null) {
    17. return txAttr;
    18. }
    19. // Transaction属性可能配置在目标类中
    20. txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
    21. if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
    22. return txAttr;
    23. }
    24. // 如果找到的目标方法和当前方法不同,即当前方法为接口方法或被重写的方法
    25. if (specificMethod != method) {
    26. // 再次从原方法中查找Transaction属性信息
    27. txAttr = findTransactionAttribute(method);
    28. if (txAttr != null) {
    29. return txAttr;
    30. }
    31. // 如果还没有找到,从原方法的定义类中查找Transaction属性信息
    32. txAttr = findTransactionAttribute(method.getDeclaringClass());
    33. if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
    34. return txAttr;
    35. }
    36. }
    37. return null;
    38. }
    39. }

    在computeTransactionAttribute()方法中,会先判断对应的方法是否为public,如果不是,直接返回null,在TransactionAspectSupport.createTransactionIfNecessary()方法中就不会开启事务。

    源码如下:

    1. public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
    2. /**
    3. * 如有必要,根据给定的TransactionAttribute创建事务
    4. */
    5. @SuppressWarnings("serial")
    6. protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
    7. @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
    8. // If no name specified, apply method identification as transaction name.
    9. // 如果未指定名称,则将方法标识应用为事务名称
    10. if (txAttr != null && txAttr.getName() == null) {
    11. txAttr = new DelegatingTransactionAttribute(txAttr) {
    12. @Override
    13. public String getName() {
    14. return joinpointIdentification;
    15. }
    16. };
    17. }
    18. TransactionStatus status = null;
    19. if (txAttr != null) {
    20. if (tm != null) {
    21. // 开启事务
    22. status = tm.getTransaction(txAttr);
    23. }
    24. else {
    25. if (logger.isDebugEnabled()) {
    26. logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
    27. "] because no transaction manager has been configured");
    28. }
    29. }
    30. }
    31. // 准备TransactionInfo对象,并返回
    32. return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    33. }
    34. }

    详细见:

    【源码】Spring Data JPA原理解析之事务执行原理-CSDN博客

    所以,如果要是@Transactional事务生效,方法必须定义为public访问权限。

    无效异常

    在项目中,为了规范编程,使用了自定义异常,如果使用不当,也容易导致事务失效。

    1)首先自定义的异常不能直接继承Exception,因为事务默认只处理RuntineException或Error异常;

    2)通过@Transactional的rollbackFor参数设置回滚异常时,指定了自定义的异常;

    3.1 示例

    1. @Slf4j
    2. @Service
    3. public class GoodsService {
    4. @Resource
    5. private GoodsRepository goodsRepository;
    6. @Resource
    7. private GoodsDetailRepository goodsDetailRepository;
    8. @Transactional(rollbackFor = BusinessException.class)
    9. public GoodsEntity save(GoodsEntity entity, GoodsDetailEntity detail) {
    10. entity.setCreateTime(new Date());
    11. try {
    12. entity = goodsRepository.save(entity);
    13. detail.setId(entity.getId());
    14. detail = goodsDetailRepository.save(detail);
    15. entity.setDetail(detail);
    16. } catch (Exception e) {
    17. throw new BusinessException(e.getMessage());
    18. }
    19. // 省略其他
    20. return entity;
    21. }
    22. }

    以上代码,如果在try-catch以外报了别的异常,那么会导致事务失效。

    3.2 原理

    3.2.1 TransactionAspectSupport异常回滚处理

    TransactionAspectSupport的相关源码如下:

    1. public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
    2. /**
    3. * 执行回滚
    4. */
    5. protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
    6. if (txInfo != null && txInfo.getTransactionStatus() != null) {
    7. if (logger.isTraceEnabled()) {
    8. logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
    9. "] after exception: " + ex);
    10. }
    11. // 如果满足回滚规则
    12. if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
    13. try {
    14. // 进行事务回滚
    15. txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
    16. }
    17. catch (TransactionSystemException ex2) {
    18. logger.error("Application exception overridden by rollback exception", ex);
    19. ex2.initApplicationException(ex);
    20. throw ex2;
    21. }
    22. catch (RuntimeException | Error ex2) {
    23. logger.error("Application exception overridden by rollback exception", ex);
    24. throw ex2;
    25. }
    26. }
    27. else {
    28. // We don't roll back on this exception.
    29. // Will still roll back if TransactionStatus.isRollbackOnly() is true.
    30. try {
    31. // 如果TransactionStatus.isRollbackOnly()为true,则仍将回滚
    32. txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    33. }
    34. catch (TransactionSystemException ex2) {
    35. logger.error("Application exception overridden by commit exception", ex);
    36. ex2.initApplicationException(ex);
    37. throw ex2;
    38. }
    39. catch (RuntimeException | Error ex2) {
    40. logger.error("Application exception overridden by commit exception", ex);
    41. throw ex2;
    42. }
    43. }
    44. }
    45. }
    46. }

    当异常导致事务回滚时,要先通过TransactionAttribute.rollbackOn()判断对应异常是否满足回滚规则,如果不满足,依然会提交事务。rollbackOn()是接口方法,实现如下:

    对于@Transactional注解,使用的是RuleBasedTransactionAttribute,该类继承DefaultTransactionAttribute。

    3.2.2 RuleBasedTransactionAttribute.rollbackOn()

    对应源码如下:

    1. public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute implements Serializable {
    2. /**
    3. * 重写父类的方法,重写定义了是否需要回滚
    4. * @param ex
    5. * @return
    6. */
    7. @Override
    8. public boolean rollbackOn(Throwable ex) {
    9. if (logger.isTraceEnabled()) {
    10. logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
    11. }
    12. RollbackRuleAttribute winner = null;
    13. // deepest为最大值,相当于不管异常有多深,只要规则中有此异常都应该回滚
    14. int deepest = Integer.MAX_VALUE;
    15. // 如果有回滚规则,从列表中查找是否有能够匹配该异常的规则
    16. if (this.rollbackRules != null) {
    17. for (RollbackRuleAttribute rule : this.rollbackRules) {
    18. int depth = rule.getDepth(ex);
    19. if (depth >= 0 && depth < deepest) {
    20. deepest = depth;
    21. winner = rule;
    22. }
    23. }
    24. }
    25. if (logger.isTraceEnabled()) {
    26. logger.trace("Winning rollback rule is: " + winner);
    27. }
    28. // User superclass behavior (rollback on unchecked) if no rule matches.
    29. // 如果没有找到匹配的回滚规则,则返回父类的执行结果,即只回滚RuntimeException 或者 Error(比如OOM这种)
    30. if (winner == null) {
    31. logger.trace("No relevant rollback rule found: applying default rules");
    32. return super.rollbackOn(ex);
    33. }
    34. // 如果有匹配的回滚规则,则属于不需要回滚的,则返回false,即不回滚
    35. return !(winner instanceof NoRollbackRuleAttribute);
    36. }
    37. }

    DefaultTransactionAttribute.rollbackOn()源码如下:

    1. public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute {
    2. /**
    3. * 是否回滚。只回滚RuntimeException 或者 Error(比如OOM这种)
    4. */
    5. @Override
    6. public boolean rollbackOn(Throwable ex) {
    7. return (ex instanceof RuntimeException || ex instanceof Error);
    8. }
    9. }

    在RuleBasedTransactionAttribute.rollbackOn()方法中,会先判断异常信息是否在@Transactional中指定,如果没有,调用父类的rollbackOn()进行判断,父类DefaultTransactionAttribute的rollbackOn()方法,判断异常是否属于RuntimeException或Error,如果是,才会回滚事务。

    业务异常捕获

    不管是通过@Transactional注解实现事务还是编程式事务,都是在业务逻辑出现异常时,事务处理会捕获异常,并判断是否要进行事务回滚,然后抛出对应异常。如果业务异常自己捕获了,没有往外抛,也会导致事务失效。

    4.1 事务失效示例

    1. @Slf4j
    2. @Service
    3. public class GoodsService {
    4. @Resource
    5. private GoodsRepository goodsRepository;
    6. @Resource
    7. private GoodsDetailRepository goodsDetailRepository;
    8. @Transactional
    9. public GoodsEntity save(GoodsEntity entity, GoodsDetailEntity detail) {
    10. entity.setCreateTime(new Date());
    11. try {
    12. entity = goodsRepository.save(entity);
    13. detail.setId(entity.getId());
    14. detail = goodsDetailRepository.save(detail);
    15. entity.setDetail(detail);
    16. } catch (Exception e) {
    17. log.error(e.getMessage());
    18. return null;
    19. }
    20. return entity;
    21. }
    22. }

    4.2 事务失效修改示例

    1. @Slf4j
    2. @Service
    3. public class GoodsService {
    4. @Resource
    5. private GoodsRepository goodsRepository;
    6. @Resource
    7. private GoodsDetailRepository goodsDetailRepository;
    8. @Transactional
    9. public GoodsEntity save(GoodsEntity entity, GoodsDetailEntity detail) {
    10. entity.setCreateTime(new Date());
    11. try {
    12. entity = goodsRepository.save(entity);
    13. detail.setId(entity.getId());
    14. detail = goodsDetailRepository.save(detail);
    15. entity.setDetail(detail);
    16. } catch (Exception e) {
    17. log.error(e.getMessage());
    18. // 设置事务回滚
    19. TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    20. return null;
    21. }
    22. return entity;
    23. }
    24. }

    只需要在异常捕获的地方加上如下代码:

    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

    4.3 原理

    事务在提交之前,会再做一次是否回滚判断,源码如下:

    1. package org.springframework.transaction.support;
    2. @SuppressWarnings("serial")
    3. public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
    4. @Override
    5. public final void commit(TransactionStatus status) throws TransactionException {
    6. if (status.isCompleted()) {
    7. throw new IllegalTransactionStateException(
    8. "Transaction is already completed - do not call commit or rollback more than once per transaction");
    9. }
    10. DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
    11. // 如果本地代码设置了回滚
    12. if (defStatus.isLocalRollbackOnly()) {
    13. if (defStatus.isDebug()) {
    14. logger.debug("Transactional code has requested rollback");
    15. }
    16. processRollback(defStatus, false);
    17. return;
    18. }
    19. // 全局事务被标记为仅回滚,但事务代码请求提交
    20. if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
    21. if (defStatus.isDebug()) {
    22. logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
    23. }
    24. processRollback(defStatus, true);
    25. return;
    26. }
    27. // 提交处理
    28. processCommit(defStatus);
    29. }
    30. }

    在commit()方法中,在真正提交处理前,会先进行两个判断:

    1)defStatus.isLocalRollbackOnly()如果返回true,会执行回滚;

    2)判断!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly(),如果为true,也会执行回滚;

    内部方法调用

    5.1 示例

    1. @Service
    2. public class MemberStatisticsService {
    3. @Resource
    4. private TransactionTemplate transactionTemplate;
    5. @Resource
    6. private MemberStatisticsRepository memberStatisticsRepository;
    7. public int addStatistics(MemberStatisticsEntity entity) {
    8. boolean rs = addStatisticsBs(entity);
    9. // 省略其他
    10. return rs ? 1 : 0;
    11. }
    12. @Transactional
    13. public int addStatisticsBs(MemberStatisticsEntity entity) {
    14. // 省略其他
    15. memberStatisticsRepository.save(entity);
    16. return true;
    17. }
    18. }

    如果是通过addStatistics()方法,方法没有添加@Transactional注解,然后调用带@Transactional注解的addStatisticsBs()方法时,当addStatisticsBs()出现异常时,事务不会回滚。

    5.2 原理

    【源码】SpringBoot事务注册原理-CSDN博客

    在上面的博客中介绍了方法中添加@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拦截器,所以没有开启事务管理。

    5.3 解决办法

    针对内部方法调用,导致事务失效的问题有如下处理方案:

    1)直接在addStatistics()方法添加@Transactional事务注解;

    这种方式最为简单,但如果初衷是为了避免大事务,这种做法就没法解决大事务的问题;

    2)使用编程式事务;

    在addStatistics()方法中使用编程式事务,在事务里面调用addStatisticsBs()方法;

    3)在类中添加自身属性;

    在MemberStatisticsService中添加一个类型为MemberStatisticsService的属性memberStatisticsService,在addStatistics()方法中,使用memberStatisticsService.addStatisticsBs()调用。代码如下:

    1. @Service
    2. public class MemberStatisticsService {
    3. @Resource
    4. private TransactionTemplate transactionTemplate;
    5. @Resource
    6. private MemberStatisticsService memberStatisticsService;
    7. @Resource
    8. private MemberStatisticsRepository memberStatisticsRepository;
    9. public int addStatistics(MemberStatisticsEntity entity) {
    10. boolean rs = memberStatisticsService.addStatisticsBs(entity);
    11. // 省略其他
    12. return rs ? 1 : 0;
    13. }
    14. @Transactional
    15. public int addStatisticsBs(MemberStatisticsEntity entity) {
    16. // 省略其他
    17. memberStatisticsRepository.save(entity);
    18. return true;
    19. }
    20. }

    传播特性使用不当

    事务传播特性的详细说明见

    【源码】Spring事务之传播特性的详解-CSDN博客

    结尾

    限于篇幅,本篇先分享到这里。

    关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

  • 相关阅读:
    Android系统之编译Intel5.1问题解决
    python之Scipy
    C++回顾<二>:类-this指针-构造函数-析构函数-隐式/显式调用explicit-初始化列表 -static静态成员变量/函数-常对象|常函数
    冰冰学习笔记:Linux下的权限理解
    基于stm32控制的4G模块在设备模式下通讯
    竞赛选题 基于机器视觉的车道线检测
    latexocr安装过程中遇到的问题解决办法
    Server2安装虚拟机
    BeanFactory与ApplicationContext的区别
    十、DPDK协议栈之ddos和epoll
  • 原文地址:https://blog.csdn.net/JingAi_jia917/article/details/139656847