事务 - Transactional

@Transactional 原理源码分析

@Transactional 自己不做任何事务操作,它只是给方法挂了一个 AOP 环绕通知:调用被代理方法时进入拦截器,开事务 -> 执行业务 -> 正常提交 / 异常回滚,织入方式与 AOP-动态代理 是同一套(@EnableTransactionManagement 注册 BeanFactoryTransactionAttributeSourceAdvisor 切面,Pointcut 就是”类或方法上有没有 @Transactional”)。
真正的事务逻辑分三层:TransactionInterceptor 决定”何时”,AbstractPlatformTransactionManager 决定”怎么做”(传播行为、挂起恢复、回滚标记),DataSourceTransactionManager 落到 JDBC,本质就是 con.setAutoCommit(false) + con.commit() / con.rollback()

代码块JAVA · 115 行收起展开
// 基于本地 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 等钩子。

代码块JAVA · 178 行收起展开
// 基于本地 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 的直接操作。

代码块JAVA · 113 行收起展开
// 基于本地 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
getTransactiondoGetTransaction: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 不是无脑提交:先查 isLocalRollbackOnlyisGlobalRollbackOnly
global 标记的来历是 REQUIRED 传播下的嵌套调用:内层 @Transactional 方法与外层共用一个事务(handleExistingTransaction 最后那个 prepareTransactionStatus(..., false, ...),不是新事务),内层抛异常时它自己的切面先走 completeTransactionAfterThrowing -> rollback -> processRollback,发现 isNewTransaction() == false,无权动连接,只能 doSetRollbackOnly 在共享的 ConnectionHolder 上打标记。
如果这个异常随后被外层 catch 住,外层正常走到 commit,检出标记后改走 processRollback(defStatus, true),最后抛 UnexpectedRollbackException
高频疑问”我明明 catch 了内层异常,为什么还报 rollback-only”的出处就是这里。
为什么参与者不直接回滚?因为提交/回滚权必须唯一归属事务的开启者:内层若直接回滚连接,外层后面的 SQL 就跑在一个已回滚的事务上,语义彻底乱掉。“参与者打标记、开启者统一收尾”是模板方法给”多层方法共享一条物理事务”定下的协议。

异常路径上,completeTransactionAfterThrowingtxAttr.rollbackOn(ex) 判定,默认规则只认 RuntimeExceptionError,受检异常走 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* 钩子,上层拦截器一行不改。

延伸阅读