如何通过Hibernate获取插入与删除的行数统计
解决大型实体层级结构删除的测试验证问题
需求场景
我正在实现删除多表中大型实体层级结构的方案,需要搭建测试套件完成以下步骤:
- 用应用方法将大型实体树加载至数据库
- 刷新实体并记录插入行数
- 清空EntityManager
- 仅加载实体根节点
- 通过应用逻辑删除实体
- 断言删除行数与插入行数相等
测试无需线程安全(用例按序执行),且无法使用内存数据库做集成测试。需要一种能统计事务开始以来所有插入、删除行数的机制,支持编程式操作、JPQL、Criteria API等场景。
当前方案的局限性
目前采用StatementInspector实现统计,但无法适配批量操作、@ElementCollection及JPQL场景,核心问题:
- 仅统计SQL语句数量,而非受影响行数,批量操作一条SQL会影响多行,统计结果完全不准
@ElementCollection的插入/删除可能被Hibernate优化为批量操作,无法准确计数- JPQL批量操作的SQL识别逻辑不完善,计数错误
解决方案
一、准确统计所有插入/删除的受影响行数
放弃StatementInspector,改用Hibernate原生拦截机制或数据库系统表统计,覆盖所有操作场景:
方案1:数据库系统表统计(适配所有操作类型)
直接查询数据库自带的统计表,获取全量插入/删除行数,适合所有操作场景(包括批量、JPQL):
public class DbStatsCountVerifier { private final EntityManager em; private long initialInserts; private long initialDeletes; // 需替换为你的业务表列表 private final List<String> targetTables = List.of("root_entity", "child_entity", "root_entity_elements"); public DbStatsCountVerifier(EntityManager em) { this.em = em; resetCounters(); } private void resetCounters() { // 以MySQL为例,查询information_schema统计 initialInserts = getTotalRows("INSERT"); initialDeletes = getTotalRows("DELETE"); } private long getTotalRows(String operation) { String sql = String.format("SELECT SUM(ROWS_AFFECTED) FROM information_schema.PROCESSLIST WHERE INFO LIKE '%%%s%%' AND DB = DATABASE()", operation); return em.createNativeQuery(sql, Long.class).getSingleResult(); } public void afterInsert() { em.flush(); // 可在此缓存插入行数 } public void assertCountsMatch() { em.flush(); long insertDiff = getTotalRows("INSERT") - initialInserts; long deleteDiff = getTotalRows("DELETE") - initialDeletes; assert deleteDiff == insertDiff : "删除行数与插入行数不匹配"; } }
注:不同数据库的统计表语法不同,需根据实际使用的数据库(PostgreSQL/Oracle等)调整SQL。
方案2:Hibernate Interceptor拦截实体事件(精准统计ORM操作)
通过Hibernate的EmptyInterceptor拦截实体生命周期,覆盖普通实体、@ElementCollection的插入/删除:
public class EntityOperationInterceptor extends EmptyInterceptor { private long insertCount = 0; private long deleteCount = 0; @Override public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { insertCount++; return super.onSave(entity, id, state, propertyNames, types); } @Override public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { deleteCount++; super.onDelete(entity, id, state, propertyNames, types); } // 处理@ElementCollection的集合修改 @Override public boolean onCollectionUpdate(Object collection, Serializable key) { if (collection instanceof PersistentCollection pc) { if (!pc.wasInitialized()) pc.initialize(); insertCount += pc.getAddedSize(); deleteCount += pc.getRemovedSize(); } return super.onCollectionUpdate(collection, key); } public long getInsertCount() { return insertCount; } public long getDeleteCount() { return deleteCount; } public void reset() { insertCount = 0; deleteCount = 0; } }
注册拦截器(Spring Boot环境可通过配置类注入):
<property name="hibernate.session_factory.interceptor" value="your.package.EntityOperationInterceptor"/>
测试中使用:
// 初始化拦截器 EntityOperationInterceptor interceptor = em.unwrap(Session.class).getInterceptor(); interceptor.reset(); // 执行实体插入逻辑 em.flush(); long insertTotal = interceptor.getInsertCount(); // 执行删除逻辑 em.flush(); long deleteTotal = interceptor.getDeleteCount(); assert deleteTotal == insertTotal;
二、确保深度对象树被完整删除
从配置和测试验证两方面保障层级结构全量删除:
1. 实体映射配置级联删除
在实体类中配置级联规则,确保删除根节点时自动清理所有关联数据:
@Entity public class RootEntity { // 级联删除子实体,同时启用孤儿移除 @OneToMany(mappedBy = "root", cascade = CascadeType.REMOVE, orphanRemoval = true) private List<ChildEntity> children; // ElementCollection默认会级联删除集合表数据 @ElementCollection private List<String> extraElements; }
2. 测试验证所有关联表为空
删除操作完成后,直接查询所有相关业务表的行数,确保全部清零:
public void verifyHierarchyDeleted() { long rootCount = em.createQuery("SELECT COUNT(r) FROM RootEntity r", Long.class).getSingleResult(); long childCount = em.createQuery("SELECT COUNT(c) FROM ChildEntity c", Long.class).getSingleResult(); long elementCount = em.createNativeQuery("SELECT COUNT(*) FROM root_entity_extra_elements", Long.class).getSingleResult(); assert rootCount == 0; assert childCount == 0; assert elementCount == 0; }
内容的提问来源于stack exchange,提问作者Adrian Jałoszewski
相关产品推荐
相关产品推荐

