事务 - Transactional
@Transactional 原理源码分析
@Transactional 自己不做任何事务操作,它只是给方法挂了一个 AOP 环绕通知:调用被代理方法时进入拦截器,开事务 -> 执行业务 -> 正常提交 / 异常回滚,织入方式与 AOP-动态代理 是同一套(@EnableTransactionManagement 注册 BeanFactoryTransactionAttributeSourceAdvisor 切面,Pointcut 就是”类或方法上有没有 @Transactional”)。
真正的事务逻辑分三层:TransactionInterceptor 决定”何时”,AbstractPlatformTransactionManager 决定”怎么做”(传播行为、挂起恢复、回滚标记),DataSourceTransactionManager 落到 JDBC,本质就是 con.setAutoCommit(false) + con.commit() / con.rollback()。
代码块收起展开
// 基于本地 Spring 仓 (JavaSourceReadingLab, spring-tx 5.x), org.springframework.transaction.interceptor.TransactionInterceptor
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
// ...
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed(); // proceed = 沿拦截器链继续走, 最终到达真正的业务方法
}
// ... getTarget()/getArguments() 省略
});
}
// ...
}
// 同文件目录下的父类 TransactionAspectSupport: 环绕逻辑全在这个模板方法里
@Nullable
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); // 解析注解的 propagation/isolation/rollbackFor, 结果有缓存
final TransactionManager tm = determineTransactionManager(txAttr); // JDBC 下就是 DataSourceTransactionManager
// ... 响应式事务(ReactiveTransactionManager)分支省略
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, 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(); // 业务方法内部再调 @Transactional 方法, 会重新进这套流程
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex); // 异常不一定回滚: 由 rollbackOn 规则决定回滚还是照样提交
throw ex; // 处理完原样往外抛, 事务切面不吞异常
}
finally {
cleanupTransactionInfo(txInfo); // 恢复 ThreadLocal 里外层的 TransactionInfo: 嵌套事务靠这个栈式还原
}
// ... Vavr Try 返回值特殊处理省略
commitTransactionAfterReturning(txInfo); // 正常返回才提交; 注意 commit 内部也可能转成回滚(rollback-only)
return retVal;
}
// ... else: CallbackPreferringPlatformTransactionManager(WebSphere 专用)分支省略
}
@SuppressWarnings("serial")
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
// If no name specified, apply method identification as transaction name.
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) {
status = tm.getTransaction(txAttr); // 传播行为在这一步生效, 进入下面的 AbstractPlatformTransactionManager
}
// ... 没配事务管理器时仅打日志, 省略
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); // 无论是否真开了事务都 bindToThread() 压栈, 保证 ThreadLocal 栈完整
}
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
// ... 日志省略
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { // 默认实现只对 RuntimeException 和 Error 返回 true
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 {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); // 坑: 不满足回滚规则的异常(典型是受检异常), 这里走的是提交
}
// ... catch 结构同上, 省略
}
}
}事务管理器这层是模板方法模式:getTransaction / commit / rollback 都是 final 骨架,处理传播、挂起、同步回调这些与具体资源无关的逻辑,子类只实现 doBegin / doCommit 等钩子。
代码块收起展开
// 基于本地 Spring 仓 (JavaSourceReadingLab, spring-tx 5.x), org.springframework.transaction.support.AbstractPlatformTransactionManager
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
Object transaction = doGetTransaction(); // JDBC 下 = 去 ThreadLocal 摸当前线程有没有已绑定的连接
boolean debugEnabled = logger.isDebugEnabled();
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(def, transaction, debugEnabled);
}
// ... timeout 合法性校验省略
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { // 没有外层事务时这三种传播行为完全等价: 都开新事务
SuspendedResourcesHolder suspendedResources = suspend(null);
// ... 日志省略
try {
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources); // 开事务失败要把挂起的资源接回来
throw ex;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
// ... SUPPORTS / NOT_SUPPORTED / NEVER: 以"空事务"继续裸跑, 省略
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); // 第三参 true = newTransaction: 提交/回滚权归它
doBegin(transaction, definition); // 子类钩子: JDBC 在这里借连接 + 关自动提交
prepareSynchronization(status, definition); // 把隔离级别/只读/事务名登记进 TransactionSynchronizationManager 那组 ThreadLocal
return status;
}
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
// ... 日志省略
Object suspendedResources = suspend(transaction); // 挂起 = 把连接从 ThreadLocal 解绑, 存进 holder, 完事再绑回去
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(
definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
// ... 日志省略
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
return startTransaction(definition, transaction, debugEnabled, suspendedResources); // 新事务会另借一条全新连接, 老连接在 holder 里等它结束
}
catch (RuntimeException | Error beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
}
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
// ... 日志省略
if (useSavepointForNestedTransaction()) {
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint(); // NESTED 不换连接也不算新事务: 同一连接上打 SAVEPOINT, 回滚只退到存档点
return status;
}
// ... JTA 场景走真嵌套 begin/commit, 省略
}
// PROPAGATION_REQUIRED, PROPAGATION_SUPPORTS, PROPAGATION_MANDATORY:
// regular participation in existing transaction.
// ... 日志与 validateExistingTransaction 兼容性校验(默认关闭)省略
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); // false = 只是参与者, 没有提交/回滚权
}
@Override
public final void commit(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) { // 业务代码手动 setRollbackOnly() 过
// ... 日志省略
processRollback(defStatus, false);
return;
}
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { // 内层参与者打过 rollback-only 标记, 外层想 commit 也会被改成回滚
// ... 日志省略
processRollback(defStatus, true); // unexpected = true: 回滚完还要向调用者抛 UnexpectedRollbackException
return;
}
processCommit(defStatus); // 真正提交: beforeCommit/beforeCompletion 回调 -> doCommit -> afterCommit/afterCompletion
}
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
// ... 日志省略
status.rollbackToHeldSavepoint(); // NESTED: 只退到存档点, 外层事务继续活着
}
else if (status.isNewTransaction()) {
// ... 日志省略
doRollback(status); // 只有事务的开启者才真的回滚连接
}
else {
// Participating in larger transaction
if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
// ... 日志省略
doSetRollbackOnly(status); // 参与者无权回滚, 只能在共享的 ConnectionHolder 上打 rollback-only 标记
}
// ... else 仅日志, 省略
}
// ... 无事务可回滚时仅日志, 省略
// Unexpected rollback only matters here if we're asked to fail early
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// Raise UnexpectedRollbackException if we had a global rollback-only marker
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
cleanupAfterCompletion(status); // 释放资源 + 若有挂起的外层事务在这里 resume 绑回
}
}最底层的 JDBC 实现,do* 钩子在这里落地成对 Connection 的直接操作。
代码块收起展开
// 基于本地 Spring 仓 (JavaSourceReadingLab, spring-jdbc 5.x), org.springframework.jdbc.datasource.DataSourceTransactionManager
@Override
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource()); // 从 ThreadLocal<Map<DataSource, ConnectionHolder>> 里找当前线程的连接
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
@Override
protected boolean isExistingTransaction(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); // "已在事务里" = 线程绑了连接且标记了 transactionActive
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection(); // 从连接池借连接: 从这一刻起被本事务独占, 到 commit/rollback 才还
// ... 日志省略
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); // 应用 isolation/readOnly, 记住旧值以便完事还原
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
// ... 日志省略
con.setAutoCommit(false); // "开启事务"的全部魔法就这一行
}
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout); // timeout 是 Spring 自己记在 holder 上查的软超时, 不下发给数据库
}
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); // 绑进 ThreadLocal: 同线程内 MyBatis/JdbcTemplate 拿的都是这一条连接
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
@Override
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
// ... 日志省略
try {
con.commit(); // 到底了: 声明式事务最终就是一句 JDBC commit; doRollback 同构, 是 con.rollback()
}
catch (SQLException ex) {
throw translateException("JDBC commit", ex);
}
}
// ... doRollback / doSuspend / doResume / doSetRollbackOnly 省略
@Override
protected void doCleanupAfterCompletion(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// Remove the connection holder from the thread, if exposed.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.unbindResource(obtainDataSource());
}
// Reset connection.
Connection con = txObject.getConnectionHolder().getConnection();
try {
if (txObject.isMustRestoreAutoCommit()) {
con.setAutoCommit(true); // 连接要还池子, 改过的状态必须全部复原, 不能污染下一个借用者
}
DataSourceUtils.resetConnectionAfterTransaction(
con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
}
catch (Throwable ex) {
logger.debug("Could not reset JDBC Connection after transaction", ex);
}
if (txObject.isNewConnectionHolder()) {
// ... 日志省略
DataSourceUtils.releaseConnection(con, this.dataSource); // 归还连接池
}
txObject.getConnectionHolder().clear();
}原理串讲
走一遍最典型的链路:Controller 调 Service 的 @Transactional 方法。
调用打到的是代理对象,进 TransactionInterceptor.invoke,再进父类的 invokeWithinTransaction。
它先用 TransactionAttributeSource 解析出这个方法的事务属性(结果按方法缓存,注解解析只付一次成本),determineTransactionManager 选出事务管理器,然后 createTransactionIfNecessary -> tm.getTransaction(txAttr) 进入 AbstractPlatformTransactionManager。
getTransaction 先 doGetTransaction:JDBC 实现去 TransactionSynchronizationManager 的 ThreadLocal 里摸当前线程有没有绑定的 ConnectionHolder,isExistingTransaction 据此判断”当前线程是否已在事务里”。
假设没有、传播是默认的 REQUIRED,走 startTransaction -> doBegin:从连接池借一条 Connection,setAutoCommit(false),把 holder bindResource 进 ThreadLocal。
为什么用 ThreadLocal 绑连接,而不是把 Connection 当参数一路传下去?因为声明式事务的卖点就是业务代码零感知:Service、DAO 的方法签名里不出现任何事务对象,MyBatis / JdbcTemplate 执行 SQL 时通过 DataSourceUtils.getConnection 从同一个 ThreadLocal 拿连接,这才保证一个事务内的所有 SQL 落在同一条物理连接上。
代价是”同一个线程”成了隐含前提:事务方法里 new Thread 或丢线程池的任务,子线程摸不到这条连接,各自拿新连接自动提交,事务对它们静默失效。
接着 invocation.proceedWithInvocation() 执行业务方法。
正常返回则 commitTransactionAfterReturning -> tm.commit()。
注意 commit 不是无脑提交:先查 isLocalRollbackOnly 和 isGlobalRollbackOnly。
global 标记的来历是 REQUIRED 传播下的嵌套调用:内层 @Transactional 方法与外层共用一个事务(handleExistingTransaction 最后那个 prepareTransactionStatus(..., false, ...),不是新事务),内层抛异常时它自己的切面先走 completeTransactionAfterThrowing -> rollback -> processRollback,发现 isNewTransaction() == false,无权动连接,只能 doSetRollbackOnly 在共享的 ConnectionHolder 上打标记。
如果这个异常随后被外层 catch 住,外层正常走到 commit,检出标记后改走 processRollback(defStatus, true),最后抛 UnexpectedRollbackException。
高频疑问”我明明 catch 了内层异常,为什么还报 rollback-only”的出处就是这里。
为什么参与者不直接回滚?因为提交/回滚权必须唯一归属事务的开启者:内层若直接回滚连接,外层后面的 SQL 就跑在一个已回滚的事务上,语义彻底乱掉。“参与者打标记、开启者统一收尾”是模板方法给”多层方法共享一条物理事务”定下的协议。
异常路径上,completeTransactionAfterThrowing 用 txAttr.rollbackOn(ex) 判定,默认规则只认 RuntimeException 和 Error,受检异常走 else 分支照样 commit。
为什么默认不回滚受检异常?这是从 EJB 沿袭的约定:受检异常在方法签名里声明过,被视为”业务上预期、调用方会处理的正常结果”(比如余额不足),只有未预期的运行时异常才推定数据已不可信。
工程上这是最大的坑,所以 rollbackFor = Exception.class 基本应当作默认配置写。
无论提交还是回滚,最后都进 cleanupAfterCompletion -> doCleanupAfterCompletion:解绑 ThreadLocal、恢复 autoCommit 和隔离级别、把连接还给池子;若之前有被挂起的外层事务,resume 把老 holder 绑回来。
REQUIRES_NEW 的实现就是 suspend + startTransaction:挂起只是把 holder 从 ThreadLocal 摘下存进 SuspendedResourcesHolder,新事务另借一条连接。
所以 REQUIRES_NEW 期间一个线程同时占两条连接,连接池太小时多层 REQUIRES_NEW 可能全线程互等连接,自己把自己拖死。
NESTED 则不换连接:createAndHoldSavepoint 在原连接上打 SAVEPOINT,内层失败 rollbackToHeldSavepoint 只退到存档点,外层不受牵连。
设计取舍
- 自调用失效:
this.b()走的是原始对象不经过代理,b 上的@Transactional完全不生效;解法是拆到别的 bean 或注入自身代理。
根因见 AOP-动态代理。
同理,非 public 方法上的注解默认也不生效。 - 回滚规则:默认只回滚
RuntimeException/Error;catch 掉不抛则切面根本看不到异常,也不回滚(但共享事务里内层切面可能已打过 rollback-only 标记)。想全回滚就显式rollbackFor = Exception.class。 - 传播行为记两个就够:REQUIRED(默认,父子同事务,一损俱损)和 REQUIRES_NEW(独立连接独立提交,常用于”无论主流程成败都要落的日志/流水”);后者有双连接占用的死锁风险。
- 事务粒度:
doBegin借的连接要一直占到 commit 才还,@Transactional方法里混进 RPC、文件 IO、慢查询会把连接池拖爆。大事务拆小,事务里只留数据库操作。 - 模板方法的边界:
getTransaction/commit/rollback是 final 的,传播语义对所有资源统一;换成 JPA、JTA 只是换一组do*钩子,上层拦截器一行不改。