Hibernate 5.6迁移至6.2时@ElementCollection原生查询加载问题
Hibernate 5到6迁移中@ElementCollection原生查询懒加载问题解决
问题场景
从Hibernate 5.6迁移到6.2时,遇到@ElementCollection懒加载异常,以下是最小复现示例:
实体类代码
@Entity @Getter @Setter public class EntityWithPrimitiveCollection { @Id @GeneratedValue private Long id; private String x; @ElementCollection @OrderColumn private List<String> ys; }
原生查询代码
public EntityWithPrimitiveCollection findByX(String x) { var query = entityManager.unwrap(Session.class).createNativeQuery( """ SELECT {e.*}, y.entity_with_primitive_collection_id AS {y.key}, y.ys_order AS {y.index}, y.ys AS {y.element} FROM entity_with_primitive_collection AS e INNER JOIN entity_with_primitive_collection_ys AS y ON e.id = y.entity_with_primitive_collection_id WHERE e.x = :x """) .setParameter("x", x); query.addRoot("e", EntityWithPrimitiveCollection.class); query.addJoin("y", "e.ys"); return (EntityWithPrimitiveCollection) query.getSingleResult(); }
注:实际查询逻辑复杂,无法通过改写HQL+抓取图解决。
在Hibernate 5中,该查询可正常加载实体及ys集合,无额外SQL;但Hibernate 6中ys被设为懒加载代理,后续触发LazyInitializationException。
问题解答
1. Hibernate 5中的方案是否合法?
这个方案在Hibernate 5中是合法且被官方支持的,并非偶然生效。Hibernate 5的原生查询关联处理逻辑里,针对@ElementCollection这类集合,只要通过addJoin关联并正确映射{y.key}、{y.index}、{y.element}这些列,就能自动把查询结果中的集合数据填充到实体中,不会保留懒加载代理。
2. Hibernate 6中的正确方案是什么?
Hibernate 6对原生查询的集合关联处理逻辑做了调整,需要显式指定集合抓取模式,或者通过自定义逻辑手动组装集合。推荐两种可行方案:
方案一:显式设置JOIN_FETCH抓取模式
在添加Join后,通过setFetchMode强制抓取集合:
public EntityWithPrimitiveCollection findByX(String x) { var query = entityManager.unwrap(Session.class).createNativeQuery( """ SELECT {e.*}, y.entity_with_primitive_collection_id AS {y.key}, y.ys_order AS {y.index}, y.ys AS {y.element} FROM entity_with_primitive_collection AS e INNER JOIN entity_with_primitive_collection_ys AS y ON e.id = y.entity_with_primitive_collection_id WHERE e.x = :x """) .setParameter("x", x); query.addRoot("e", EntityWithPrimitiveCollection.class); query.addJoin("y", "e.ys") .setFetchMode("e.ys", FetchMode.JOIN); // 新增该行强制抓取集合 return (EntityWithPrimitiveCollection) query.getSingleResult(); }
方案二:自定义ResultTransformer组装实体和集合
如果方案一不生效,可以通过ResultTransformer手动处理结果集,把关联的ys数据填充到实体中:
public EntityWithPrimitiveCollection findByX(String x) { var query = entityManager.unwrap(Session.class).createNativeQuery( """ SELECT e.id, e.x, y.ys_order, y.ys FROM entity_with_primitive_collection AS e INNER JOIN entity_with_primitive_collection_ys AS y ON e.id = y.entity_with_primitive_collection_id WHERE e.x = :x """) .setParameter("x", x); return query.setResultTransformer(new ResultTransformer() { private final Map<Long, EntityWithPrimitiveCollection> entityMap = new HashMap<>(); @Override public Object transformTuple(Object[] tuple, String[] aliases) { Long id = (Long) tuple[0]; String xVal = (String) tuple[1]; Integer order = (Integer) tuple[2]; String yVal = (String) tuple[3]; EntityWithPrimitiveCollection entity = entityMap.get(id); if (entity == null) { entity = new EntityWithPrimitiveCollection(); entity.setId(id); entity.setX(xVal); entity.setYs(new ArrayList<>()); entityMap.put(id, entity); } // 按order顺序填充元素 while (entity.getYs().size() <= order) { entity.getYs().add(null); } entity.getYs().set(order, yVal); return entity; } @Override public List transformList(List collection) { return new ArrayList<>(entityMap.values()); } }).getSingleResult(); }
注意:Hibernate 6中
ResultTransformer已标记为过时,但对于复杂原生查询场景仍是有效方案;若追求更优雅的实现,也可使用TupleTransformer配合自定义逻辑完成。
内容的提问来源于stack exchange,提问作者Begemoth
相关产品推荐
相关产品推荐

