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

Spring Boot自定义JPA事务管理器触发无限递归问题求助

问题描述与解决方案

问题背景

我有一个基于Spring Boot 3.1.4的Web应用,使用Hibernate和JDBC-backed Session持久化。自定义了JPA事务管理器CustomJPATransactionManager,希望在事务启动时向JDBC连接会话注入自定义连接变量(但不需要对会话持久化所用的JDBC连接执行此操作),代码实现如下:

public class CustomJPATransactionManager extends JpaTransactionManager {

    private static final Logger logger = LoggerFactory.getLogger(CustomJPATransactionManager.class);

    @Autowired
    private EntityManager entityManager;

    @Override
    protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
        super.prepareSynchronization(status, definition);
        if (status.isNewTransaction()) {
            logger.trace("start of new JPA Transaction");
            // current transaction is suspended and a new one opened right as the security context is accessed
            User user = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            if (user != null) {
                Session session = entityManager.unwrap(Session.class);
                String userId = user.getId() != 0 ? String.valueOf(user.getId()) : "-2";
                logger.trace(String.format("userId: %s", userId));
                session.doWork(connection -> {
                    try (Statement statement = connection.createStatement()) {
                        statement.execute(String.format("SELECT set_config('var.context-id', '%s', %s)", userId,"true"));
                    }
                });
            }
        }
    }
}

遇到的问题

Security Context通过JDBC存储访问,该存储会参与事务管理,因此当尝试访问Security Context时,Spring会挂起当前事务并启动新事务,日志如下:

CustomJPATransactionManager : Found thread-bound EntityManager [SessionImpl(354629356PersistenceContext[entityKeys=[], collectionKeys=[]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])] for JPA transaction
CustomJPATransactionManager : Suspending current transaction, creating new transaction with name [null]

这会触发无限递归:当前事务被挂起,新事务启动时又会调用同一个事务管理器,最终导致超时。

疑问

  1. 是否有办法缓解此行为?能否让Spring Session访问不使用与业务逻辑EntityManager相同的JpaTransactionManager?
  2. 考虑将会话信息缓存到Session作用域或Request作用域变量中,让事务管理器自动装配该变量,此方案是否可行?是否需要用ThreadLocal变量?

解决方案

一、分离Spring Session与业务逻辑的事务管理器

完全可以实现两者的事务管理器隔离:

  • 为Spring Session单独配置一套DataSource和JpaTransactionManager,确保其EntityManagerFactory与业务逻辑的实例完全独立。
  • 在Spring Session的配置类或相关DAO层,通过@Transactional(transactionManager = "sessionTransactionManager")指定使用独立的事务管理器;或者在Spring Session的配置中直接绑定该事务管理器Bean的名称。
  • 这样当Security Context从JDBC存储加载时,会使用独立的事务链路,不会触发业务事务管理器的逻辑,从根源避免递归挂起事务的问题。

二、缓存会话信息到Request作用域/ThreadLocal

该方案可行,且是更轻量的解决方式:

  • Request作用域:在请求入口的过滤器(如OncePerRequestFilter)中,提前加载Security Context的用户信息,存入Request作用域变量。事务管理器直接从Request作用域读取用户ID,无需再访问SecurityContextHolder,避免触发JDBC查询和事务操作。
  • ThreadLocal:如果存在异步线程场景,Request作用域无法覆盖时,可使用ThreadLocal存储用户ID。同样在请求入口过滤器初始化,请求结束时清理ThreadLocal中的值,防止内存泄漏。事务管理器直接读取ThreadLocal中的值,绕开SecurityContext的JDBC访问链路。

三、调整事务管理器逻辑,延迟变量注入时机

修改prepareSynchronization方法的逻辑,将连接变量的注入延迟到第一次执行业务SQL时:

  • 通过Hibernate的Interceptor或Spring的EntityManagerInterceptor实现,在SQL执行前触发连接变量的设置。这样就不会在事务启动阶段触发SecurityContext的加载,避免事务递归问题。

内容的提问来源于stack exchange,提问作者alessandro ferrucci

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 20:56:33