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
相关产品推荐
相关产品推荐

