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

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上下文:

  1. 在SpringBoot启动类添加@EnableAsync注解开启异步支持
  2. 修改监听器方法:
@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 15:25:35