深入解析 @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方法,并将其视为一个整体进行代理,所以此种场景下并不会导致事务的失效。

相关推荐
一只叫煤球的猫4 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9654 小时前
tcp/ip 中的多路复用
后端
bobz9654 小时前
tls ingress 简单记录
后端
皮皮林5516 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友6 小时前
什么是OpenSSL
后端·安全·程序员
bobz9656 小时前
mcp 直接操作浏览器
后端
前端小张同学8 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook8 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康9 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在9 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net