深入解析 @Transactional 作用于私有方法时事务失效的原因

摘要:从一个简单的例子入手,由浅入深的从源码角度剖析@Transactional标注于private方法失效的原因。

前言

@TransactionalSpring框架中用于管理事务的注解。其可以用于标记普通方法,以使用Spring的声明式事务管理功能。从而将事务的管理交于Spring处理,以自动地在方法执行前后自动处理事务的开启、提交和回滚等操作,而无需显式地编写事务管理代码。

虽然@Transactional的注解的引入极大的简化了我们对于事务的控制,但如果错误的使用@Transactional或者对@Transactional的机制理解不透彻,反而会适得其反。例如,如下的这段代码:

java 复制代码
 
public class UserInfoServiceImpl implements IUserInfoService {
 
  @Transactional
  public Result<String> addUserInfo(UserInfo userInfo) {
   // ...省略部分业务逻辑 ...
 
  // 调用addUserAccountInfo 方法完成用户账户积分信息保存
  addUserAccountInfo(userInfo);
  
  return Result.success("success");
 }

 
 public void addUserAccountInfo(UserInfo userInfo) {
    // .....完成相关账户信息的业务操作......
 }
    
}

上述代码中,addUserInfo方法通过@Transactional进行修饰,即期待如果其内部出现异常能实现数据的回滚,从而避免数据不一致的产生 。如果有看过Java面试的八股文,你可能会认为由于addUserAccountInfo通过private修饰,如果addUserAccountInfo内部逻辑出现异常,则会导致@Transactional的失效。

这主要是因为很多面试的八股文早已对@Transactional注解的失效场景进行了汇总,其中有一点便是:@Transactional用于private方法时,会导致@Transactional失效。

事实上,很多人只记住一个@Transactional无法用于private注解,但对其究竟哪种情况会失效却不甚清楚。

今天我们便来看一看@Transactional用于private方法失效的场景究竟是什么。

@Transactional无法作用于private失效的原因

众所周知,在Spring应用中,如果要开启对事务@Transactional的支持,需要我们在启动类上加入@EnableTransactionManagement,从而开启Spring 的事务管理功能。

进一步,当使用 @EnableTransactionManagement 后,SpringBean加载过程时,才会扫描容器中所有带有 @Transactional 注解的方法,并其生成代理类,从而为其添加事务管理的增强点。

所以,要清楚@Transactional的解析的秘密,我们以@EnableTransactionManagement作为入口在合适不过了,其代码如下:

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
 // ... 省略其他逻辑    
}

事实上,对于@EnableTransactionManagement注解而言,其会通过@Import注入一个名为TransactionManagementConfigurationSelector的配置类。进一步,在TransactionManagementConfigurationSelector配置类中,其会向Spring容器注入一个名为ProxyTransactionManagementConfiguration配置类,这部分具体逻辑如下:

java 复制代码
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> 
{
   @Override
   protected String[] selectImports(AdviceMode adviceMode) {
      switch (adviceMode) {
         case PROXY:
            return new String[] {AutoProxyRegistrar.class.getName(),
                  ProxyTransactionManagementConfiguration.class.getName()};
         case ASPECTJ:
            return new String[] {determineTransactionAspectClass()};
         default:
            return null;
      }
   }

(注:这里注入逻辑会涉及到SpringBoot的自动装配机制,对于此我们在此便不进行赘述了,不了解的朋友可自行查阅相关资料~)

我们接着来看注入的ProxyTransactionManagementConfiguration的内部逻辑:

java 复制代码
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration 

     extends AbstractTransactionManagementConfiguration {
  
   @Bean
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
      TransactionInterceptor interceptor = new TransactionInterceptor();
      interceptor.setTransactionAttributeSource(transactionAttributeSource);
      if (this.txManager != null) {
         interceptor.setTransactionManager(this.txManager);
      }
      return interceptor;
   }

}

可以看到,其内部会注入一个类型为TransactionInterceptorBean实例。而TransactionInterceptorSpring框架中的一个核心类,其主要作为一个拦截器,用以拦截被 @Transactional 注解标注的方法调用,并根据事务的配置来执行事务的开启、提交和回滚等操作。

进一步, TransactionInterceptor类中invokeWithinTransaction 是整个类的核心方法,执行被事务管理的方法,并根据事务配置对事务进行控制也是通过此方法来完成。其逻辑如下:

TransactionInterceptor # invokeWithinTransaction

java 复制代码
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
      final InvocationCallback invocation) throws Throwable {

   // If the transaction attribute is null, the method is non-transactional.
   TransactionAttributeSource tas = getTransactionAttributeSource();
   final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
   final TransactionManager tm = determineTransactionManager(txAttr);

   // ... 省略无关代码

   if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
      // Standard transaction demarcation with getTransaction and commit/rollback calls.
      TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

      Object retVal;
      try {
         // This is an around advice: Invoke the next interceptor in the chain.
         // This will normally result in a target object being invoked.
         retVal = invocation.proceedWithInvocation();
      }
      catch (Throwable ex) {
         // target invocation exception
         completeTransactionAfterThrowing(txInfo, ex);
         throw ex;
      }
      

  // ... 省略无关代码
}

可以看到, 在invokeWithinTransaction内部,其会首先获取TransactionAttributeSource对象,进而通过其获取TransactionAttribute。事实上,在Spring的事务管理机制中,TransactionAttributeSourceTransactionAttribute 是两个紧密关联的接口。

其中TransactionAttributeSource 的主要作用是确定某个类或方法是否配置了事务属性 ,以及获取这些事务的配置细节。而TransactionAttribute 则用于表示事务的具体配置。

例如,事务的传播行为、隔离级别、超时时间、是否只读等。它都是封装了事务相关配置的对象,供Spring事务管理器使用,以控制事务的行为。换言之,TransactionAttributeSource负责从 @Transactional 注解或XML配置中解析事务相关的元数据,并生成相应的 TransactionAttribute 实例。

进一步来看,其中TransactionAttributeSourcegetTransactionAttribute逻辑如下:

java 复制代码
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
   
      // ... 省略无关代码
      TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
     // ... 省略无关代码
      return txAttr;
   }
}

可以看到,在TransactionAttributeSourcegetTransactionAttribute内部,其又会将获取TransactionAttribute逻辑交给computeTransactionAttribute。其逻辑如下:

java 复制代码
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
   // Don't allow non-public methods, as configured.
   if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
   }

  
// ... 省略无关代码
   return null;
}

不难发现,computeTransactionAttribute方法会判断@Transactional修饰的方法是否为public修饰,如果不是则返回null

更进一步,如果获取到的TransactionInfonull时,则invokeWithinTransaction内部执行方法如果遇到异常,执行 completeTransactionAfterThrowing方法。会指定其中的else逻辑,进而也就导致事务不会回滚。这部分逻辑如下。

java 复制代码
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
   if (txInfo != null && txInfo.getTransactionStatus() != null) {
        // ... 省略无关逻辑
   
        txInfo.getTransactionManager()
        .rollback(txInfo.getTransactionStatus());

      } else {
         // We don't roll back on this exception.
          // ... 省略无关逻辑
          txInfo.getTransactionManager()
          .commit(txInfo.getTransactionStatus());
         
      }
   }
}

为了方便理解,笔者上述过程整个调用逻辑进行了总结,具体如下图所示:

总结

事实上,对于Spring内部的事务机制而言,其内部在进行事务自动化处理时大致逻辑如下:

  1. 获取事务管理器;
  2. 创建事务;
  3. 执行目标方法;
  4. 捕捉异常,判断是否执行回滚或是提交;
  5. 提交事务;

至此,我们就对Spring中内部的事务机制进行了大致的介绍,同时基于一个简单的例子,我们对@Transactional作用于private方法上导致事务失效的原因进行了深入的剖析。事实上,事务失效的场景主要针对@Transactional直接作用于private方法上的场景。

而对于@Transactional作用于public方法,而该public方法再调用非public方法的场景却不会失效。究其原因主要在于其在构建代理逻辑拦截时,拦截的是被@Transactional所修改的外部public方法,并将其视为一个整体进行代理,所以此种场景下并不会导致事务的失效。

相关推荐
阿伟*rui12 分钟前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
码农小旋风4 小时前
详解K8S--声明式API
后端
Peter_chq4 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7895 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot