我在
Spring Boot 3.1.4
中有一个使用 Hibernate 和 JDBC 支持的会话持久性的 Web 应用程序,我有一个自定义 JPA 事务管理器,我希望每次事务启动时都可以使用它来将自定义连接变量注入到我的 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"));
}
});
}
}
}
}
我遇到的问题是,对安全上下文的访问是通过 JDBC 存储完成的,它与其他一切一样参与事务管理,因此,当我尝试访问安全上下文时,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 会话访问不使用与我们的“一般业务逻辑”EntityManager 相同的 JpaTransactionManager 吗?
或者 - 我正在考虑将会话信息缓存在会话范围或请求范围的变量中,该 Jpa 事务管理器可以自动装配,这可以工作吗?它需要是 ThreadLocal 定义的变量吗?
感谢您的宝贵时间。
可能会迟到,但最近遇到了类似的问题,我的应用程序有 3 个事务管理器用于 3 个不同的数据源。
Spring Session 在配置自身时需要一个
PlatformTransactionManager
bean 位于上下文中。在您的情况下,Spring Session 似乎正在使用您的自定义 CustomJPATransactionManager
来处理 JDBC 支持的 Spring Session 表上自己的事务。
您可以做的是创建一个新的 TransactionManager bean,以便 Spring Session 使用它。像这样:
@Bean
@Primary
public PlatformTransactionManager springSessionTransactionmanager(DataSource datasource) {
return new DataSourceTransactionManager(datasource);
}
必须使用
@Primary
注释来注释 Spring Session 才能拾取它。如果没有 @Primary
Spring Session 将不知道要自动装配到哪个 Transactionmanager。
参考资料:
这里是 Spring Session 用于设置所有内容的 JdbcHttpSessionConfiguration.java
类的链接。您可以看到 Spring Session (JDBC) 尝试在 PlatformTransactionManager 中自动装配的部分:
@Autowired
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}