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,兼容性更好,维护成本更低。