摘要:从一个简单的例子入手,由浅入深的从源码角度剖析
@Transactional
标注于private
方法失效的原因。
前言
@Transactional
是Spring
框架中用于管理事务的注解。其可以用于标记普通方法,以使用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
后,Spring
在Bean
加载过程时,才会扫描容器中所有带有 @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;
}
}
可以看到,其内部会注入一个类型为TransactionInterceptor
的Bean
实例。而TransactionInterceptor
是Spring
框架中的一个核心类,其主要作为一个拦截器,用以拦截被 @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
的事务管理机制中,TransactionAttributeSource
和 TransactionAttribute
是两个紧密关联的接口。
其中TransactionAttributeSource
的主要作用是确定某个类或方法是否配置了事务属性 ,以及获取这些事务的配置细节。而TransactionAttribute
则用于表示事务的具体配置。
例如,事务的传播行为、隔离级别、超时时间、是否只读等。它都是封装了事务相关配置的对象,供Spring
事务管理器使用,以控制事务的行为。换言之,TransactionAttributeSource
负责从 @Transactional
注解或XML配置中解析事务相关的元数据,并生成相应的 TransactionAttribute
实例。
进一步来看,其中TransactionAttributeSource
的getTransactionAttribute
逻辑如下:
java
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// ... 省略无关代码
TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
// ... 省略无关代码
return txAttr;
}
}
可以看到,在TransactionAttributeSource
的getTransactionAttribute
内部,其又会将获取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
。
更进一步,如果获取到的TransactionInfo
为null
时,则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
内部的事务机制而言,其内部在进行事务自动化处理时大致逻辑如下:
- 获取事务管理器;
- 创建事务;
- 执行目标方法;
- 捕捉异常,判断是否执行回滚或是提交;
- 提交事务;
至此,我们就对Spring
中内部的事务机制进行了大致的介绍,同时基于一个简单的例子,我们对@Transactional
作用于private
方法上导致事务失效的原因进行了深入的剖析。事实上,事务失效的场景主要针对@Transactional
直接作用于private
方法上的场景。
而对于@Transactional
作用于public
方法,而该public
方法再调用非public
方法的场景却不会失效。究其原因主要在于其在构建代理逻辑拦截时,拦截的是被@Transactional
所修改的外部public
方法,并将其视为一个整体进行代理,所以此种场景下并不会导致事务的失效。