Hibernate查询如何处理EntityNotFoundException保留有效数据
Hibernate分页查询过滤断关联无效实体方案
问题根因
原实现直接调用getResultList()批量加载实体时,Hibernate会在结果集遍历阶段统一初始化关联代理,只要任意一条记录存在断引用触发EntityNotFoundException,整个结果集遍历流程就会终止,外层异常捕获直接返回空列表,最终导致整页有效数据被丢弃。
使用@NotFound注解虽然能抑制异常,但会强制对应关联改为急加载,额外带来不必要的性能开销,不符合需求。
最优实现方案
将整页批量加载拆分为先查ID、再单条加载的两步逻辑,单条记录加载失败直接过滤,不影响同页其他数据,同时完全不需要修改实体注解,避免急加载副作用:
private List<? extends Entity> findAll(Class entity, Integer startIndex, Integer pageSize) throws EntityIndexingServiceException { try { // 第一步:仅查询当前页的实体主键,不触发任何关联加载,不会被断关联影响 String idQueryStr = "select entity.id from " + entity.getName() + " entity order by entity.id desc"; Query idQuery = entityManager.createQuery(idQueryStr); if (startIndex != null && pageSize != null) { idQuery.setFirstResult(startIndex).setMaxResults(pageSize); } List<Serializable> pageIds = idQuery.getResultList(); List<Entity> validResult = new ArrayList<>(pageIds.size()); // 第二步:按ID逐条加载实体,单独捕获单条记录的异常 for (Serializable id : pageIds) { try { Entity instance = (Entity) entityManager.find(entity, id); if (instance == null) { continue; } // 主动初始化实体关联,提前捕获断引用异常,避免坏数据流到索引流程 // 可根据索引实际需要的字段,替换为指定关联的初始化,减少性能损耗 Hibernate.initialize(instance); validResult.add(instance); } catch (EntityNotFoundException e) { log.warn("过滤无效实体[{}],ID:{},原因:存在断关联引用", entity.getName(), id, e); } } return validResult; } catch (Throwable e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); log.warn(sw.toString()); return Collections.emptyList(); } }
可选高性能实现
如果要避免两次查询的开销,可以用ScrollableResults做流式逐行读取,每处理完一条记录就清空一级会话缓存,单条出错直接清空缓存后继续处理下一条,性能更优:
private List<? extends Entity> findAll(Class entity, Integer startIndex, Integer pageSize) { List<Entity> validResult = new ArrayList<>(); Session session = entityManager.unwrap(Session.class); try (ScrollableResults results = session.createQuery("from " + entity.getName() + " order by id desc") .setFirstResult(startIndex) .setMaxResults(pageSize) .scroll(ScrollMode.FORWARD_ONLY)) { while (results.next()) { try { Entity instance = (Entity) results.get(0); Hibernate.initialize(instance); validResult.add(instance); // 清空一级缓存,避免代理对象互相影响 session.flush(); session.clear(); } catch (EntityNotFoundException e) { log.warn("过滤无效实体,原因:断关联引用", e); session.clear(); } } } return validResult; }
注意事项
Hibernate.initialize()会初始化实体的所有直接懒加载关联,如果索引逻辑不需要全量关联字段,可以直接访问需要的关联字段触发对应代理初始化即可,减少不必要的SQL查询- 不要在异常捕获后直接继续遍历原
getResultList()的结果集,Hibernate的结果集迭代器在抛出异常后状态会损坏,继续遍历会拿到不可预期的脏数据 - 长期来看建议逐步补全数据库外键约束,从根源避免断引用数据产生
内容的提问来源于stack exchange,提问作者pbuchheit
相关产品推荐
相关产品推荐

