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]
这会触发无限递归:当前事务被挂起,新事务启动时又会调用同一个事务管理器,最终导致超时。
疑问
- 是否有办法缓解此行为?能否让Spring Session访问不使用与业务逻辑EntityManager相同的JpaTransactionManager?
- 考虑将会话信息缓存到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
相关产品推荐
相关产品推荐

