Hibernate Criteria投影查询:如何分组并获取完整实体对象?
解决Hibernate Criteria查询中获取完整Entity及关联实体最大值的问题
我明白你的痛点——现在的查询只能拿到主键和最大值的数组,没法直接得到完整的Entity对象,还得保证是单次查询对吧?下面给你两种可行的方案,都是基于Hibernate Criteria实现的:
方案一:使用子查询+自定义DTO(推荐)
这种方法先通过子查询算出每个Entity对应的AnotherEntity.n的最大值,再和主查询关联,最后用结果转换器把完整Entity和最大值映射到一个自定义的DTO类里,既清晰又高效。
步骤1:定义DTO类
先创建一个简单的DTO来承载结果,它包含完整的Entity对象和最大值属性:
public class EntityWithMaxN { private Entity entity; private Integer maxN; // 无参构造器(Hibernate的AliasToBeanResultTransformer需要) public EntityWithMaxN() {} // 带参构造器和getter/setter public EntityWithMaxN(Entity entity, Integer maxN) { this.entity = entity; this.maxN = maxN; } // getter和setter方法 public Entity getEntity() { return entity; } public void setEntity(Entity entity) { this.entity = entity; } public Integer getMaxN() { return maxN; } public void setMaxN(Integer maxN) { this.maxN = maxN; } }
步骤2:构建Criteria查询
// 1. 构建子查询:计算每个Entity对应的AnotherEntity.n的最大值 DetachedCriteria maxNSubquery = DetachedCriteria.forClass(AnotherEntity.class, "ae") .setProjection(Projections.projectionList() .add(Projections.max("n")) // 取n的最大值 .add(Projections.groupProperty("entity.pk"))) // 按Entity的主键分组 .add(Restrictions.eqProperty("entity.pk", "e.pk")); // 关联主查询的Entity // 2. 主查询:获取符合条件的完整Entity,同时关联子查询的最大值 Criteria c = session.createCriteria(Entity.class, "e"); c.add(Restrictions.eq("someEntityProperty", someEntityPropertyValue)); // 投影完整的Entity对象和子查询的最大值,并设置别名 ProjectionList projectionList = Projections.projectionList() .add(Projections.property("e"), "entity") // 投影整个Entity,别名为entity .add(Projections.subquery(maxNSubquery), "maxN"); // 投影子查询结果,别名为maxN c.setProjection(projectionList); // 使用结果转换器,把投影结果映射到DTO类 c.setResultTransformer(new AliasToBeanResultTransformer(EntityWithMaxN.class)); // 执行查询,得到包含完整Entity和最大值的DTO列表 List<EntityWithMaxN> resultList = c.list();
方案二:给Entity添加临时属性(适合不想创建DTO的场景)
如果不想额外定义DTO,可以给Entity类添加一个@Transient的临时属性来存储最大值,然后直接把结果映射回Entity对象:
步骤1:修改Entity类
添加一个临时属性,注意加上@Transient注解,避免Hibernate把它映射到数据库字段:
public class Entity { // 原有属性... private Long pk; private String someEntityProperty; private Set<AnotherEntity> anotherEntitySet; // 新增临时属性,存储AnotherEntity.n的最大值 @Transient private Integer maxN; // 原有getter/setter... public Integer getMaxN() { return maxN; } public void setMaxN(Integer maxN) { this.maxN = maxN; } }
步骤2:构建Criteria查询
// 子查询部分和方案一一致 DetachedCriteria maxNSubquery = DetachedCriteria.forClass(AnotherEntity.class, "ae") .setProjection(Projections.projectionList() .add(Projections.max("n")) .add(Projections.groupProperty("entity.pk"))) .add(Restrictions.eqProperty("entity.pk", "e.pk")); // 主查询:投影Entity的所有属性 + 最大值 Criteria c = session.createCriteria(Entity.class, "e"); c.add(Restrictions.eq("someEntityProperty", someEntityPropertyValue)); ProjectionList projectionList = Projections.projectionList(); // 投影Entity的所有字段(这里需要列出所有属性,比如pk、someEntityProperty等) projectionList.add(Projections.property("pk"), "pk"); projectionList.add(Projections.property("someEntityProperty"), "someEntityProperty"); // ... 继续添加Entity的其他属性 projectionList.add(Projections.subquery(maxNSubquery), "maxN"); // 投影最大值到临时属性 c.setProjection(projectionList); // 把结果映射回Entity对象 c.setResultTransformer(new AliasToBeanResultTransformer(Entity.class)); List<Entity> resultList = c.list();
⚠️ 注意:这种方法需要手动列出Entity的所有属性,当Entity字段较多时会比较繁琐,所以更推荐方案一。
为什么你的原代码不行?
原代码只投影了主键和最大值,没有包含Entity的其他属性,所以只能得到Object[]。要获取完整Entity,必须把Entity的所有属性(或整个对象)加入投影,再通过结果转换器把数据组装成对象。
内容的提问来源于stack exchange,提问作者Giulio Piancastelli
相关产品推荐
相关产品推荐

