part2-transaction-interceptor
约 5320 字大约 18 分钟
2026-05-24
系列文章:《深入Spring事务与数据源:从注解到数据库连接的全链路解析》
系列一第2篇:TransactionInterceptor事务拦截器详解
基于版本:Spring Framework 5.3.x + Spring Boot 2.7.x
前置阅读:第一篇:@Transactional注解的幕后英雄
前言
上篇文章咱们聊了@Transactional注解是怎么被Spring激活的,也知道了Spring会为带有这个注解的Bean创建代理对象。但是问题来了——
当咱们调用userService.createUser(user)的时候,代理对象到底干了啥?事务是怎么开启的?事务管理器在哪里?它又是怎么跟数据源扯上关系的?
别急,这篇文章咱们就一层一层地扒开TransactionInterceptor的外衣,看看它的核心方法invokeWithinTransaction到底是怎么工作的。我敢保证,看完这篇文章,你会对Spring事务有一个全新的认识!
一、先回顾一下:Spring Boot是怎么自动开启事务管理的?
在深入源码之前,咱们得先搞清楚一个问题:为什么在Spring Boot项目中,咱们什么都没配,@Transactional就能用呢?
这得从Spring Boot的自动装配说起。让我带你看看核心入口代码:
核心入口1:Spring Boot自动配置入口
文件位置:spring-boot-autoconfigure/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
# ... 其他自动配置
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
# ... 其他自动配置看到了吧?TransactionAutoConfiguration这个类被Spring Boot自动加载了。那这个类干了啥呢?
核心入口2:TransactionAutoConfiguration
文件位置:spring-boot-autoconfigure/TransactionAutoConfiguration.java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(PlatformTransactionManager.class) // 检查classpath是否有事务管理器
@AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, Neo4jDataAutoConfiguration.class })
@EnableConfigurationProperties(TransactionProperties.class) // 绑定事务配置属性
public class TransactionAutoConfiguration {
@Bean
@ConditionalOnBean(PlatformTransactionManager.class) // 必须有事务管理器Bean
@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
public TransactionManagerCustomizers transactionManagerCustomizers(
ObjectProvider<List<PlatformTransactionManagerCustomizer<?>>> customizers) {
return new TransactionManagerCustomizers(customizers.getIfAvailable());
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(PlatformTransactionManager.class) // 如果没有手动配置事务管理器
static class EnableTransactionManagementConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement(proxyTargetClass = false) // 开启事务管理,使用JDK代理
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = false)
static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement(proxyTargetClass = true) // 开启事务管理,使用CGLIB代理
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true) // 默认就是true,也就是说默认使用CGLIB代理
static class CglibAutoProxyConfiguration {
}
}
}看到没?关键点就在@EnableTransactionManagement这个注解上!这不就是咱们上篇文章分析的那个注解吗?
扩展场景1:如何自定义事务配置?
如果你想自定义事务的超时时间、回滚规则等,可以这样做:
# application.yml
spring:
transaction:
default-timeout: 30s # 默认超时30秒
rollback-on-commit-failure: true # 提交失败时回滚Spring Boot会通过TransactionProperties类读取这些配置,然后应用到事务管理器上。
扩展场景2:如何禁用Spring Boot的事务自动配置?
@SpringBootApplication(exclude = TransactionAutoConfiguration.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}不过说实话,一般没人会禁用这个配置,除非你想完全自己手动管理事务。
好了,自动装配的入口咱们搞清楚了。现在回到正题,当代理对象的方法被调用时,到底发生了什么?
二、代理对象的方法调用入口
咱们先来看个简单的业务代码:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional
public void createUser(User user) {
userMapper.insert(user);
System.out.println("用户创建成功");
}
}当你调用userService.createUser(user)的时候,实际上调用的是Spring生成的代理对象。这个代理对象会拦截方法调用,然后交给TransactionInterceptor处理。
核心方法1:TransactionInterceptor.invoke
文件位置:spring-tx/TransactionInterceptor.java
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
/**
* 这是AOP方法拦截器的入口
* 所有被@Transactional标注的方法,最终都会走到这里
*/
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取目标类(被代理的真实类)
// 比如说UserService被代理了,这里拿到的就是UserService.class
Class<?> targetClass = (invocation.getThis() != null
? AopUtils.getTargetClass(invocation.getThis()) : null);
// 核心!调用父类的事务处理方法
// 这里传入了三个参数:
// 1. invocation.getMethod():当前被调用的方法(createUser)
// 2. targetClass:目标类(UserService.class)
// 3. invocation::proceed:这是一个回调,等事务开启后会执行它来调用真实方法
return invokeWithinTransaction(
invocation.getMethod(),
targetClass,
invocation::proceed
);
}
}看到没?invoke方法很简单,就是拿到了目标类,然后把活儿都甩给了父类的invokeWithinTransaction方法。
不过这里有个细节你注意到没?invocation::proceed这个回调,它实际上就是一个函数式接口的实现,等会儿事务开启后,会调用这个回调来执行咱们的createUser方法。
扩展场景3:如果我想在事务拦截前后加自己的逻辑怎么办?
你可以自定义一个MethodInterceptor,然后把它放在TransactionInterceptor之前执行:
@Component
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE) // 确保在事务拦截器之前执行
public class CustomInterceptor {
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("事务执行前");
try {
Object result = pjp.proceed();
System.out.println("事务执行成功");
return result;
} catch (Exception e) {
System.out.println("事务执行失败:" + e.getMessage());
throw e;
}
}
}三、invokeWithinTransaction:事务管理的真正核心
好了,现在咱们进入重头戏——invokeWithinTransaction方法。这个方法可以说是整个Spring事务管理的灵魂,理解了它,你就理解了Spring事务的本质。
核心方法2:invokeWithinTransaction
文件位置:spring-tx/TransactionAspectSupport.java
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// ========== 第一步:获取事务属性 ==========
// 获取TransactionAttributeSource(事务属性源)
// 这个对象负责解析@Transactional注解上的配置
TransactionAttributeSource tas = getTransactionAttributeSource();
// 调用getTransactionAttribute解析方法上的@Transactional注解
// 返回的txAttr包含了传播行为、隔离级别、超时时间、只读标记等信息
final TransactionAttribute txAttr = (tas != null
? tas.getTransactionAttribute(method, targetClass) : null);
// ========== 第二步:确定事务管理器 ==========
// 根据txAttr中的qualifier属性,或者配置,找到对应的事务管理器
// 比如说你配置了多个数据源,就有多个事务管理器
// 这里要决定用哪个
final TransactionManager tm = determineTransactionManager(txAttr);
// 这里是处理响应式事务的(WebFlux那种)
// 咱们今天不聊这个,先跳过
if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
// ... 响应式事务处理逻辑
}
// 将TransactionManager转换为PlatformTransactionManager
// 这是咱们传统事务管理使用的接口
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
// ========== 第三步:生成事务标识(用于日志和监控) ==========
// 比如生成"com.example.UserService.createUser"这样的字符串
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// ========== 第四步:处理事务 ==========
// 判断是不是标准的PlatformTransactionManager
// 大部分情况下都是走这个分支
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// ★★★★★ 核心中的核心:创建事务(如果需要的话)★★★★★
// 这个方法会:
// 1. 调用ptm.getTransaction()获取或创建事务
// 2. 把事务信息绑定到ThreadLocal
TransactionInfo txInfo = createTransactionIfNecessary(
ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// ★★★★★ 执行业务方法 ★★★★★
// 这里会调用invocation::proceed回调
// 也就是真正执行咱们的createUser方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// ★★★★★ 发生异常时处理事务 ★★★★★
// 根据异常类型决定是回滚还是提交
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 清理事务信息(从ThreadLocal中移除)
cleanupTransactionInfo(txInfo);
}
// 处理Vavr Try类型(一种函数式编程库)
// 一般用不到,跳过
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
// ★★★★★ 提交事务 ★★★★★
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
// CallbackPreferring方式(比较少用)
// 这种方式的TransactionManager喜欢用回调
// 比如JTA事务管理器
}
}这个方法看着长,但是逻辑其实特别清晰,就四步:
- 解析事务属性:看看
@Transactional上配了啥 - 找事务管理器:确定用哪个事务管理器
- 创建事务:开启事务(如果需要的话)
- 执行方法并处理结果:调用真实方法,根据成功或失败决定提交还是回滚
是不是一下子就清晰了?别急,咱们一步步深入每个环节。
四、第一步:解析@Transactional注解上的配置
咱们先看看@Transactional注解上的配置是怎么被解析的。
核心方法3:getTransactionAttribute
文件位置:spring-tx/AnnotationTransactionAttributeSource.java
public class AnnotationTransactionAttributeSource extends AbstractTransactionAttributeSource {
// 事务属性缓存
// key是方法,value是解析后的事务属性
// 避免重复解析,提升性能
private final Map<Object, TransactionAttribute> attributeCache =
new ConcurrentHashMap<>(256);
@Override
@Nullable
public TransactionAttribute getTransactionAttribute(Method method,
@Nullable Class<?> targetClass) {
// 如果是Object类的方法(比如toString、hashCode),直接返回null
// 这些方法不需要事务
if (method.getDeclaringClass() == Object.class) {
return null;
}
// 先检查缓存,如果已经解析过了,直接返回
Object cacheKey = getCacheKey(method, targetClass);
TransactionAttribute cached = this.attributeCache.get(cacheKey);
if (cached != null) {
// 如果缓存的是NULL_TRANSACTION_ATTRIBUTE,说明这个方法没有事务属性
return (cached == NULL_TRANSACTION_ATTRIBUTE ? null : cached);
}
else {
// 缓存没命中,开始解析
TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
// 把解析结果放入缓存
// 如果没有事务属性,就放一个特殊的NULL_TRANSACTION_ATTRIBUTE标记
this.attributeCache.put(cacheKey,
(txAttr != null ? txAttr : NULL_TRANSACTION_ATTRIBUTE));
return txAttr;
}
}
}这里用了一个ConcurrentHashMap做缓存,避免每次都去解析注解。你想啊,如果一个方法被调用1000次,总不能解析1000次注解吧?缓存一下,性能提升可不是一点半点。
核心方法4:computeTransactionAttribute
文件位置:spring-tx/AnnotationTransactionAttributeSource.java
@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method,
@Nullable Class<?> targetClass) {
// 如果只允许public方法,那就检查方法的修饰符
// 默认情况下,@Transactional只能用在public方法上
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// 获取具体的方法(处理接口和实现类的情况)
// 比如UserService接口定义了createUser方法,UserServiceImpl实现了它
// 这里会拿到UserServiceImpl.createUSer
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// ★★★★★ 第一步:先查找方法上的@Transactional注解 ★★★★★
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr; // 方法上有注解,直接返回
}
// ★★★★★ 第二步:查找类上的@Transactional注解 ★★★★★
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr; // 类上有注解,返回
}
// 如果方法定义在不同接口上,检查接口方法
if (specificMethod != method) {
// 检查接口方法上的注解
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// 检查接口类上的注解
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
// 都没找到,返回null
return null;
}看到没?解析是有优先级的:
- 实现类方法上的注解(优先级最高)
- 实现类类上的注解
- 接口方法上的注解
- 接口类上的注解(优先级最低)
扩展场景4:如果接口和实现类都加了@Transactional,以哪个为准?
答案是以实现类方法上的为准!举个例子:
// 接口
public interface UserService {
@Transactional(timeout = 60) // 超时60秒
void createUser(User user);
}
// 实现类
@Service
public class UserServiceImpl implements UserService {
@Override
@Transactional(timeout = 30) // 超时30秒
public void createUser(User user) {
// 实际超时时间是30秒,不是60秒
}
}这种情况下,以实现类方法上的timeout = 30为准。
扩展场景5:非public方法上加@Transactional会怎样?
@Service
public class UserService {
@Transactional // 不会生效!
private void privateMethod() {
// 这个事务不会生效,因为方法是private的
}
@Transactional // 会生效
public void publicMethod() {
// 这个事务会正常生效
}
}默认情况下,@Transactional只能用在public方法上。如果你非要用在非public方法上,可以这样配置:
@EnableTransactionManagement
public class Config {
@Bean
public AnnotationTransactionAttributeSource transactionAttributeSource() {
// 设置allowPublicMethodsOnly为false
return new AnnotationTransactionAttributeSource(false);
}
}不过说实话,真不推荐这么做,违反了Spring的设计初衷。
五、第二步:确定使用哪个事务管理器
好,事务属性解析完了,接下来要确定用哪个事务管理器。你可能会问,事务管理器不是就一个吗?为啥还要确定?
那是因为——在多数据源场景下,你可能有多个事务管理器!
核心方法5:determineTransactionManager
文件位置:spring-tx/TransactionAspectSupport.java
@Nullable
protected TransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
// 如果没有事务属性或者没有BeanFactory,直接返回默认事务管理器
if (txAttr == null || this.beanFactory == null) {
return getTransactionManager();
}
// ★★★★★ 策略1:检查@Transactional注解中是否指定了value/qualifier ★★★★★
// 比如@Transactional("primaryTxManager")
String qualifier = txAttr.getQualifier();
if (StringUtils.hasText(qualifier)) {
return determineQualifiedTransactionManager(this.beanFactory, qualifier);
}
// ★★★★★ 策略2:检查@EnableTransactionManagement中是否指定了transactionManagerBeanName ★★★★★
else if (StringUtils.hasText(this.transactionManagerBeanName)) {
return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
}
// ★★★★★ 策略3:使用默认事务管理器 ★★★★★
else {
TransactionManager defaultTransactionManager = getTransactionManager();
if (defaultTransactionManager == null) {
// 从缓存中获取
defaultTransactionManager = this.transactionManagerCache.get(
DEFAULT_TRANSACTION_MANAGER_KEY);
if (defaultTransactionManager == null) {
// 从Spring容器中按类型查找PlatformTransactionManager
defaultTransactionManager = this.beanFactory.getBean(
TransactionManager.class);
// 放入缓存,避免下次再查找
this.transactionManagerCache.putIfAbsent(
DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
}
}
return defaultTransactionManager;
}
}查找策略分三步:
- 看
@Transactional("xxx")有没有指定事务管理器名称 - 看配置类有没有指定默认事务管理器
- 从Spring容器中按类型查找
扩展场景6:多数据源场景下如何指定事务管理器?
@Configuration
public class DataSourceConfig {
// 主数据源
@Bean
@Primary
public DataSource primaryDataSource() {
return new DruidDataSource();
}
@Bean
@Primary
public PlatformTransactionManager primaryTxManager() {
return new DataSourceTransactionManager(primaryDataSource());
}
// 副数据源
@Bean
public DataSource secondaryDataSource() {
return new DruidDataSource();
}
@Bean
public PlatformTransactionManager secondaryTxManager() {
return new DataSourceTransactionManager(secondaryDataSource());
}
}
@Service
public class UserService {
// 使用主数据源的事务
@Transactional("primaryTxManager")
public void createUser(User user) {
// 这个操作使用主数据源
}
// 使用副数据源的事务
@Transactional("secondaryTxManager")
public void createUserInSecondary(User user) {
// 这个操作使用副数据源
}
}看到没?通过在@Transactional中指定事务管理器的名称,就可以灵活地选择不同的数据源。
六、第三步:创建事务(核心中的核心)
好了,重头戏来了!事务是怎么创建的?
核心方法6:createTransactionIfNecessary
文件位置:spring-tx/TransactionAspectSupport.java
@SuppressWarnings("serial")
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
// 如果没有指定事务名称,就用方法的全限定名作为事务名称
// 比如"com.example.UserService.createUser"
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) {
// ★★★★★ 核心调用:获取事务 ★★★★★
// 这里会调用AbstractPlatformTransactionManager.getTransaction()
// 这个方法是事务管理的真正核心!
status = tm.getTransaction(txAttr);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
// 准备事务信息
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}关键就在这一行:status = tm.getTransaction(txAttr)
这里调用的是PlatformTransactionManager接口的getTransaction方法,而实际执行的是它的实现类AbstractPlatformTransactionManager。
不过这个方法咱们放在下一篇文章详细讲,因为它的内容实在是太多了,包括:
- 如何判断是否存在现有事务
- 七种传播行为怎么处理
- 如何开启新事务
- 如何挂起现有事务
这里咱们先知道它会返回一个TransactionStatus对象,这个对象代表了当前事务的状态。
核心方法7:prepareTransactionInfo
文件位置:spring-tx/TransactionAspectSupport.java
protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, String joinpointIdentification,
@Nullable TransactionStatus status) {
// 创建TransactionInfo对象
// 这个对象封装了事务的所有信息
TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
if (txAttr != null) {
if (logger.isTraceEnabled()) {
logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
// 设置事务状态
txInfo.newTransactionStatus(status);
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No need to create transaction for [" + joinpointIdentification +
"]: This method is not transactional.");
}
}
// ★★★★★ 关键:将TransactionInfo绑定到ThreadLocal ★★★★★
txInfo.bindToThread();
return txInfo;
}最后这一步txInfo.bindToThread()非常重要,它把事务信息绑定到了当前线程的ThreadLocal上。
核心方法8:bindToThread
文件位置:spring-tx/TransactionAspectSupport.java
private void bindToThread() {
// 保存旧的TransactionInfo(支持嵌套事务)
this.oldTransactionInfo = transactionInfoHolder.get();
// 设置新的TransactionInfo
transactionInfoHolder.set(this);
}这里用了一个ThreadLocal来保存事务信息,确保同一个线程内可以访问到同一个事务。
七、第四步:执行业务方法
事务创建好了,接下来就该执行咱们的业务方法了。
try {
// 调用invocation.proceedWithInvocation()
// 这个回调最终会执行到咱们的createUser方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 如果发生异常,进入异常处理流程
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 无论成功还是失败,都要清理事务信息
cleanupTransactionInfo(txInfo);
}invocation.proceedWithInvocation()这个回调,最终会调用到invocation.proceed(),也就是执行拦截器链中的下一个拦截器,或者直接执行目标方法。
八、第五步:异常处理和事务回滚
如果业务方法抛出了异常,会怎么处理呢?
核心方法9:completeTransactionAfterThrowing
文件位置:spring-tx/TransactionAspectSupport.java
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);
}
// ★★★★★ 判断是否应该回滚 ★★★★★
// 默认情况下,RuntimeException和Error会回滚
// 受检异常(Exception)不会回滚
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 {
// 不回滚,反而提交
// 你没看错,有些异常是不需要回滚的!
try {
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;
}
}
}
}看到这里你可能会问:什么?发生异常了居然不一定回滚?
没错!默认情况下:
- RuntimeException和Error:会回滚
- 受检异常(Exception的子类,但不是RuntimeException):不会回滚,反而提交!
扩展场景7:如何让受检异常也回滚?
@Service
public class UserService {
// 方式1:在@Transactional中指定rollbackFor
@Transactional(rollbackFor = Exception.class)
public void createUser(User user) throws Exception {
// 现在抛出Exception也会回滚了
throw new Exception("故意抛出的异常");
}
// 方式2:指定noRollbackFor让RuntimeException不回滚
@Transactional(noRollbackFor = NullPointerException.class)
public void createUser2(User user) {
// 现在抛出NullPointerException也不会回滚
throw new NullPointerException("空指针");
}
}你说Spring为啥这么设计?我琢磨着可能是这样的:受检异常通常是可以恢复的,比如网络超时,重试一下就行了,没必要回滚;而RuntimeException通常是程序bug,必须回滚。
九、第六步:提交事务
如果业务方法正常执行完了,没有抛异常,就会提交事务。
核心方法10:commitTransactionAfterReturning
文件位置:spring-tx/TransactionAspectSupport.java
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
// 调用事务管理器的commit方法
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}这里调用了PlatformTransactionManager.commit()方法,实际的提交逻辑在AbstractPlatformTransactionManager中实现。这个咱们也放到下一篇文章详细讲。
十、完整的执行流程图
说了这么多,让我画个图帮你理一理整个流程:
调用:userService.createUser(user)
↓
┌─────────────────────────────────────┐
│ 代理对象(JDK Proxy / CGLIB) │
│ 拦截方法调用 │
└─────────────┬───────────────────────┘
↓
┌─────────────────────────────────────┐
│ TransactionInterceptor.invoke() │
│ │
│ 1. 获取目标类 │
│ 2. 调用invokeWithinTransaction() │
└─────────────┬───────────────────────┘
↓
┌─────────────────────────────────────┐
│ invokeWithinTransaction() │
│ │
│ 第一步:获取事务属性 │
│ → getTransactionAttribute() │
│ → 解析@Transactional注解 │
│ → 提取传播行为、隔离级别等 │
│ → 缓存到attributeCache │
└─────────────┬───────────────────────┘
↓
┌─────────────────────────────────────┐
│ 第二步:确定事务管理器 │
│ → determineTransactionManager() │
│ → 查找策略: │
│ 1. @Transactional("xxx")指定 │
│ 2. 配置指定 │
│ 3. 按类型查找 │
└─────────────┬───────────────────────┘
↓
┌─────────────────────────────────────┐
│ 第三步:创建事务 │
│ → createTransactionIfNecessary() │
│ → tm.getTransaction(txAttr) │
│ → 调用AbstractPlatform... │
│ → 返回TransactionStatus │
│ │
│ → prepareTransactionInfo() │
│ → 创建TransactionInfo │
│ → bindToThread()绑定到ThreadLocal │
└─────────────┬───────────────────────┘
↓
┌─────────────────────────────────────┐
│ 第四步:执行业务方法 │
│ → invocation.proceedWithInvocation()│
│ → 调用userMapper.insert(user) │
│ → 执行SQL │
└─────────────┬───────────────────────┘
↓ (正常执行)
┌─────────────────────────────────────┐
│ 第五步:提交事务 │
│ → commitTransactionAfterReturning()│
│ → tm.commit(status) │
│ → 下一篇文章详解 │
└─────────────────────────────────────┘
↓ (发生异常)
┌─────────────────────────────────────┐
│ 异常处理:回滚或提交 │
│ → completeTransactionAfterThrowing()│
│ → 判断rollbackOn(ex) │
│ → 回滚:tm.rollback(status) │
│ → 提交:tm.commit(status) │
└─────────────────────────────────────┘十一、关键问题解答
Q1:为什么一个事务方法里调用另一个事务方法,传播行为会生效?
@Transactional
public void methodA() {
methodB(); // 调用另一个事务方法
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 会挂起methodA的事务,创建新事务
}答案:因为methodB()被调用时,同样会经过代理对象,进入TransactionInterceptor.invoke(),然后调用tm.getTransaction()。这时候AbstractPlatformTransactionManager会发现已经有一个事务了,根据REQUIRES_NEW传播行为,就会挂起旧事务,创建新事务。
Q2:如果方法上没有@Transactional,但是类上有,会怎样?
答案:会生效!解析事务属性的时候,如果方法上没找到,会继续找类上的注解。
Q3:TransactionInfo为什么要绑定到ThreadLocal?
答案:因为事务是跟线程绑定的。同一个线程内的所有操作,都应该使用同一个事务。绑定到ThreadLocal后,无论在哪个地方,都能通过transactionInfoHolder.get()获取到当前线程的事务信息。
Q4:如果事务方法返回值是void,事务还会提交吗?
答案:会的!事务的提交与否跟返回值没关系,只跟是否抛异常有关。只要方法正常执行完(没抛异常),就会提交。
十二、总结与伏笔
通过这篇文章的深入分析,咱们完整追踪了事务开启的全过程:
- 代理拦截:TransactionInterceptor.invoke()拦截方法调用
- 属性解析:从@Transactional注解提取事务配置,带缓存优化
- 管理器查找:根据qualifier或类型找到合适的事务管理器
- 创建事务:调用tm.getTransaction()创建事务
- 信息绑定:将TransactionInfo绑定到ThreadLocal
- 执行业务:调用真实的业务方法
- 异常处理:根据异常类型决定回滚还是提交
- 事务提交:正常完成则提交事务
但是!最核心的问题咱们还没解答:
tm.getTransaction()到底干了啥?
- 它是怎么判断是否存在现有事务的?
- 七种传播行为是怎么实现的?
- 事务是怎么挂起和恢复的?
- 新事务是怎么开启的?
这些问题,将在下一篇文章《事务管理器的核心:AbstractPlatformTransactionManager的事务生命周期》中为你揭晓。到时候咱们会深入到AbstractPlatformTransactionManager的源码,看看这个事务管理的"大脑"是怎么工作的。
敬请期待!
参考资料
- Spring Framework 5.3.x 源码
spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.javaspring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.javaspring-tx/src/main/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSource.java
- Spring Boot 2.7.x 源码
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/TransactionAutoConfiguration.java
- 《Spring源码深度解析》