2. 实体定义

我们以一个简单的 Product 实体为例,它包含以下字段:

@Entity
public class Product {
    @Id
    private long id;
    
    private String name;
    
    private String description;
    
    private String category;
    
    private BigDecimal unitPrice;

    // setters and getters
}

这是一个典型的 JPA 实体类,用来映射数据库中的产品表。


3. JPA 投影

虽然 JPA 规范中没有明确提到“投影”这个术语,但在实际开发中,投影是非常常见的一种需求 —— 只查询部分字段,而不是整个实体对象

这在优化性能、减少数据库 IO 上非常有用,尤其适用于只需要部分字段的场景。

3.1 单字段投影

比如我们只需要查询所有产品的名称:

Query query = entityManager.createQuery("select name from Product");
List<String> resultList = query.getResultList();

也可以使用 Criteria API 实现:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<String> query = builder.createQuery(String.class);
Root<Product> product = query.from(Product.class);
query.select(product.get("name"));
List<String> resultList = entityManager.createQuery(query).getResultList();

✅ 技巧:因为只选了一个字段,且是 String 类型,所以我们可以直接指定返回类型为 String

输出结果类似:

Product Name 1
Product Name 2
Product Name 3
Product Name 4

⚠️ 注意:如果选的是 id,返回类型就是 Long

3.2 多字段投影

如果我们想同时查询 id, name, unitPrice 等字段:

JPQL 写法:

Query query = entityManager.createQuery("select id, name, unitPrice from Product");
List<Object[]> resultList = query.getResultList();

Criteria API 写法:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
Root<Product> product = query.from(Product.class);
query.multiselect(product.get("id"), product.get("name"), product.get("unitPrice"));
List<Object[]> resultList = entityManager.createQuery(query).getResultList();

✅ 注意:这里使用了 multiselect() 方法,返回的是 Object[] 数组。

输出结果类似:

[1, Product Name 1, 1.40]
[2, Product Name 2, 4.30]
[3, Product Name 3, 14.00]
[4, Product Name 4, 3.90]

⚠️ 踩坑提醒:处理 Object[] 不太方便。推荐使用 CriteriaBuilder.construct()Tuple 来映射到自定义类。

3.3 聚合函数投影

除了查询字段,我们也可以投影聚合函数的结果,比如统计每个分类的产品数量:

JPQL 写法:

Query query = entityManager.createQuery("select p.category, count(p) from Product p group by p.category");

Criteria API 写法:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
Root<Product> product = query.from(Product.class);
query.multiselect(product.get("category"), builder.count(product));
query.groupBy(product.get("category"));

输出结果类似:

[category1, 2]
[category2, 1]
[category3, 1]

Hibernate CriteriaBuilder 支持的聚合函数包括:

  • avg():平均值
  • max():最大值
  • min():最小值
  • sum():求和
  • count():计数

4. Hibernate 投影

Hibernate 有自己的 Criteria 查询方式(注意:从 Hibernate 5.2 开始已不推荐使用,推荐使用 JPA 的 CriteriaQuery API),但它的投影功能依然强大。

4.1 单字段投影

使用 SingularAttribute 来指定字段:

final CriteriaBuilder criteria = session.getCriteriaBuilder();
final CriteriaQuery<String> criteriaQuery = criteria.createQuery(String.class);
final Root<Product> root = criteriaQuery.from(Product.class);
final SingularAttribute<Product, String> name = Product_.name;
final Path<String> nameProjection = root.get(name);
criteriaQuery.select(nameProjection);

4.2 多字段投影

多个字段需要多次调用 root.get()

final CriteriaBuilder criteria = session.getCriteriaBuilder();
final CriteriaQuery<Object[]> criteriaQuery = criteria.createQuery(Object[].class);
final Root<Product> root = criteriaQuery.from(Product.class);
final SingularAttribute<Product, String> name = Product_.name;
final SingularAttribute<Product, Long> id = Product_.id;
final Path<String> nameProjection = root.get(name);
final Path<Long> idProjection = root.get(id);
criteriaQuery.multiselect(idProjection, nameProjection);

4.3 聚合函数投影

统计每个分类的产品数量:

final CriteriaBuilder criteria = session.getCriteriaBuilder();
final CriteriaQuery<Object[]> criteriaQuery = criteria.createQuery(Object[].class);
final Root<Product> root = criteriaQuery.from(Product.class);
criteriaQuery.groupBy(root.get("category"));
criteriaQuery.multiselect(root.get("category"), criteria.count(root));

4.4 使用别名进行排序

Hibernate 支持为投影字段设置别名,这对排序和条件查询非常有用:

final CriteriaBuilder criteria = session.getCriteriaBuilder();
final CriteriaQuery<Object[]> criteriaQuery = criteria.createQuery(Object[].class);
final Root<Product> root = criteriaQuery.from(Product.class);
criteriaQuery.groupBy(root.get("category"));
criteriaQuery.multiselect(root.get("category"), criteria.count(root));
criteriaQuery.orderBy(criteria.asc(criteria.count(root)));

5. 总结

本文我们学习了如何使用 JPA 和 Hibernate 进行投影查询,包括:

  • 单字段投影 ✅
  • 多字段投影 ✅
  • 聚合函数投影 ✅
  • Hibernate Criteria 投影 ✅

⚠️ 注意:Hibernate 自己的 Criteria API 在 5.2 版本后已不推荐使用,推荐使用 JPA 的 CriteriaQuery API。

虽然 Hibernate 团队不再维护自己的 Criteria API,但其投影功能依然强大,尤其适合已有代码中继续使用。

✅ 建议:对于新项目,优先使用 JPA 的 Criteria API,兼容性更好,维护成本更低。


原始标题:JPA/Hibernate Projections | Baeldung