You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SPR-16876:AbstractRoutingDataSource中TransactionSynchronizationManager返回值异常

问题分析与解决方案

核心问题

在非JPA/Hibernate的Spring Boot主从库场景中,使用@Transactional(readOnly = true)标注的服务方法,在RoutingDataSource的determineCurrentLookupKey()方法中调用TransactionSynchronizationManager.isCurrentTransactionReadOnly()始终返回false,getCurrentTransactionName()返回null;但在服务方法内部调用这两个方法却能得到正确结果,导致只读事务始终路由到主库(读写实例)。

这是Spring Framework的已知时序问题(对应内部编号SPR-16876):当使用DataSourceTransactionManager(非JPA事务管理器)时,事务的只读属性与事务名称会延迟到获取数据库连接后才被设置到TransactionSynchronizationManager中,而RoutingDataSource需要在获取连接前确定数据源,因此无法拿到已初始化的事务上下文。

解决方案

方案1:升级Spring Framework版本

该问题已在Spring Framework 5.1及以上版本中被修复。升级后,DataSourceTransactionManager会在事务启动阶段就将只读属性同步到TransactionSynchronizationManager,此时RoutingDataSource的determineCurrentLookupKey()可以正确读取到isCurrentTransactionReadOnly()的返回值,无需额外修改代码。

方案2:自定义线程标记与AOP切面(兼容低版本Spring)

如果无法升级Spring版本,可通过自定义线程本地存储(ThreadLocal)结合AOP切面,提前标记只读事务状态:

  1. 定义只读事务标记工具类
public class ReadOnlyTransactionMarker {
    private static final ThreadLocal<Boolean> READ_ONLY_FLAG = new ThreadLocal<>();

    public static void setReadOnly(boolean readOnly) {
        READ_ONLY_FLAG.set(readOnly);
    }

    public static boolean isReadOnly() {
        Boolean flag = READ_ONLY_FLAG.get();
        return flag != null && flag;
    }

    public static void clear() {
        READ_ONLY_FLAG.remove();
    }
}
  1. 编写AOP切面拦截事务方法
    在事务启动前设置只读标记,事务结束后清理:
@Aspect
@Component
public class ReadOnlyTransactionAspect {
    // 拦截所有标注@Transactional的方法
    @Before("@annotation(org.springframework.transaction.annotation.Transactional) && @annotation(tx)")
    public void beforeTransactionExecution(JoinPoint joinPoint, Transactional tx) {
        ReadOnlyTransactionMarker.setReadOnly(tx.readOnly());
    }

    @After("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void afterTransactionExecution() {
        ReadOnlyTransactionMarker.clear();
    }
}
  1. 修改RoutingDataSource的路由逻辑
    使用自定义标记替代TransactionSynchronizationManager的判断:
public class MasterSlaveRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return ReadOnlyTransactionMarker.isReadOnly() ? "SLAVE" : "MASTER";
    }
}

方案3:自定义TransactionInterceptor(进阶)

通过扩展TransactionInterceptor,在事务初始化阶段提前将只读属性同步到线程上下文,确保RoutingDataSource能读取到:

public class PreSetReadOnlyTransactionInterceptor extends TransactionInterceptor {
    @Override
    protected Object invokeWithinTransaction(Method method, Class<?> targetClass, InvocationCallback invocation) throws Throwable {
        TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
        if (txAttr != null) {
            // 提前设置只读标记到TransactionSynchronizationManager
            TransactionSynchronizationManager.setCurrentTransactionReadOnly(txAttr.isReadOnly());
            TransactionSynchronizationManager.setCurrentTransactionName(txAttr.getName());
        }
        try {
            return super.invokeWithinTransaction(method, targetClass, invocation);
        } finally {
            // 清理上下文
            TransactionSynchronizationManager.clearCurrentTransactionReadOnly();
            TransactionSynchronizationManager.clearCurrentTransactionName();
        }
    }
}

随后在事务管理器配置中替换默认的TransactionInterceptor即可。


内容的提问来源于stack exchange,提问作者Harsh Kanakhara

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 19:45:26