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

Kotlin中Hibernate简单集成测试失败:持久化后无法删除实体

问题描述

我编写了一个测试用例,用于验证刚创建的实体被删除后是否真正从数据库中移除。实体类代码如下:

@Entity
class BigClass(@Column(nullable = false)
               val name: String,

               @OneToMany(orphanRemoval = true, fetch = FetchType.EAGER, cascade = [CascadeType.ALL]) 
               val map: Set<MapEntry>) : PanacheEntity



@Entity
class MapEntry(@OneToOne(orphanRemoval = true, cascade = [CascadeType.ALL])
               val keyList: Keys,

               @OneToOne(orphanRemoval = true, cascade = [CascadeType.ALL]) 
               val valueList: Values) : PanacheEntity


@Entity
class Keys(@OneToMany(orphanRemoval = true, cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
           val list: Set<Key>) : PanacheEntity


@Entity
class Values(@OneToMany(orphanRemoval = true, cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
             val list: Set<Value>) : PanacheEntity


@Entity
class Key(@Column(nullable = false) 
          val name: String,

          @ElementCollection(fetch = FetchType.EAGER) 
          val ids: Set<String>) : PanacheEntity


@Entity
class Value(@Column(nullable = false) 
            val name: String,

            @Column(nullable = false) 
            val description: String) : PanacheEntity

测试代码如下:

@BeforeAll
fun setup() {
    val ef = Persistence.createEntityManagerFactory("test")
    sessionFactory = ef.unwrap(SessionFactory::class.java)
}    


suspend fun create(entity: BigClass): String {
    sessionFactory.withTransaction { s ->
            s.persist(entity)
        }.awaitSuspending()
        return entity.id.toString()
}


suspend fun delete(id: String) {
        sessionFactory.withTransaction { s ->
            s.find(BigClass::class.java, id.toLong())
            .map { s.remove(it) }
        }.awaitSuspending()
}

suspend fun read(id: String): BigClass? {
    return sessionFactory.withTransaction { s ->
        s.find(BigClass::class.java, id.toLong())
     }.awaitSuspending()
} 



@Test
fun createAndDelete() {
    runBlocking {
        val id = create(testEntity)
        delete(id)

        val actual = read(id)

        assertEquals(null, actual)   // 实际返回的仍是被删除的testEntity
    }
}

执行测试后发现,删除操作完成后读取到的实体依然存在,测试失败,请问这是什么原因?


原因分析与解决方案

这个问题的核心原因是Hibernate的一级缓存(Session缓存)机制:

  • 缓存复用导致读取旧数据:create方法中持久化实体后,该实体会被存入当前Session的一级缓存。尽管create、delete、read各自开启了独立事务,但如果测试环境下SessionFactory复用了Session实例,read操作会直接从缓存中读取之前的实体,不会去数据库查询最新状态。
  • 删除后缓存未清理:即使删除事务提交成功,Session缓存中的实体对象并不会自动被移除,后续查询仍会优先命中缓存。

解决方法:

1. 删除时同步数据库并清除缓存

修改delete方法,在事务内主动刷新数据库操作并清除Session缓存:

suspend fun delete(id: String) {
    sessionFactory.withTransaction { s ->
        s.find(BigClass::class.java, id.toLong())
            .map { s.remove(it) }
        s.flush() // 立即将删除操作同步到数据库
        s.clear() // 清除Session缓存,避免后续读取旧数据
    }.awaitSuspending()
}

2. 查询时强制从数据库刷新

在read方法中,即使缓存存在实体,也强制从数据库刷新状态:

suspend fun read(id: String): BigClass? {
    return sessionFactory.withTransaction { s ->
        val entity = s.find(BigClass::class.java, id.toLong())
        entity?.let { s.refresh(it) } // 强制从数据库拉取最新状态
        entity
    }.awaitSuspending()
}

3. 确保测试操作使用独立Session

配置SessionFactory为每个事务提供全新的Session实例,避免缓存跨事务复用。比如在每次操作后关闭Session,或者调整Session的获取策略。

另外,也可以检查测试数据库的事务隔离级别,确保删除事务提交后,后续查询能立即看到数据库的最新状态。


内容的提问来源于stack exchange,提问作者David Tomecek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 08:55:17