SpringBoot中@PostRemove监听器出现possible nonthreadsafe access to session错误
Hibernate批量删除触发@PostRemove监听器时的线程安全问题排查与解决
问题描述
在SpringBoot应用中,为NordigenConnection实体配置了@PostRemove事件监听器,代码如下:
@EntityListeners(NordigenConnectionListener::class) @Entity class NordigenConnection( ... ) @Component private class NordigenConnectionListener { private val nordigenConnectionService: NordigenConnectionService by autowired() @PostRemove fun onPostRemove(nordigenConnection: NordigenConnection) { nordigenConnectionService.deleteConnectionDataInNordigen(nordigenConnection) } }
注:NordigenConnectionService采用自定义方式注入,Spring无法通过标准方式注入事件监听器,此部分并非问题原因。
在另一服务执行批量删除NordigenConnections操作时,单个实体删除正常,但批量删除时第二个监听器调用会在deleteConnectionDataInNordigen方法的nordigenTokenRepository.findByClient(nordigenConnection.client)处失败,报错:
org.hibernate.AssertionFailure: possible nonthreadsafe access to session
当前未手动管理Session,且请求为单线程运行,需明确问题根源及解决办法。Hibernate版本:5.6.14
1. 问题根源
- Hibernate在批量删除场景下会复用同一个Session实例,而
@PostRemove监听器中执行的数据库操作(如findByClient)会尝试在该Session上进行操作。即使请求是单线程,批量删除时Hibernate内部会快速切换Session状态以处理多个实体的删除事件,导致Session状态不一致,触发线程安全断言。 - 单个删除时,Hibernate会为实体删除流程维护清晰的Session上下文;但批量删除时,Session会被复用处理多个实体,监听器中调用Repository方法时,Session可能正处于前一个实体删除的收尾状态,引发状态冲突。
2. 解决办法
方法一:使用独立Session执行监听器操作
注入EntityManagerFactory,在监听器方法中手动创建新的EntityManager(即独立Session),并在独立事务中执行操作,避免与当前批量删除的Session冲突:
@Component private class NordigenConnectionListener { @Autowired private lateinit var entityManagerFactory: EntityManagerFactory private val nordigenConnectionService: NordigenConnectionService by autowired() @PostRemove fun onPostRemove(nordigenConnection: NordigenConnection) { val entityManager = entityManagerFactory.createEntityManager() val transaction = entityManager.transaction try { transaction.begin() // 通过新EntityManager获取Repository实例,或直接执行JPQL val tokenRepo = entityManager.getReference(NordigenTokenRepository::class.java) val tokens = tokenRepo.findByClient(nordigenConnection.client) // 执行后续删除逻辑 nordigenConnectionService.deleteConnectionDataInNordigen(nordigenConnection, entityManager) transaction.commit() } catch (e: Exception) { if (transaction.isActive) transaction.rollback() throw e } finally { entityManager.close() } } }
方法二:异步执行监听器操作
通过@Async将监听器方法标记为异步,让操作在独立线程中执行,脱离当前批量删除的Session上下文:
- 在SpringBoot启动类添加
@EnableAsync注解开启异步支持 - 修改监听器方法:
@Component private class NordigenConnectionListener { private val nordigenConnectionService: NordigenConnectionService by autowired() @PostRemove @Async fun onPostRemove(nordigenConnection: NordigenConnection) { nordigenConnectionService.deleteConnectionDataInNordigen(nordigenConnection) } }
注意:需在deleteConnectionDataInNordigen方法上添加@Transactional,确保异步操作在独立事务中执行。
方法三:手动处理前置逻辑,跳过监听器
执行批量删除前,先查询所有待删除实体,手动调用删除逻辑,再执行批量删除,避免触发监听器:
// 批量删除服务中的代码 fun batchDeleteConnections(client: Client) { val connections = nordigenConnectionRepository.findByClient(client) connections.forEach { nordigenConnectionService.deleteConnectionDataInNordigen(it) } nordigenConnectionRepository.deleteAllByClient(client) }
此方式逻辑更可控,完全避免Session冲突问题。
内容的提问来源于stack exchange,提问作者David Kubecka
相关产品推荐
相关产品推荐

