1. 概述
本文将深入探讨 JPA 中的一个强大功能——Criteria 查询。它允许我们以面向对象的方式构建查询,无需编写原生 SQL 语句,这正是 Hibernate 的核心优势之一。Criteria API 让我们能通过编程方式构建查询对象,灵活应用各种过滤条件和逻辑规则。
⚠️ 自 Hibernate 5.2 起,Hibernate 的 Criteria API 已被弃用,新开发应转向 JPA Criteria API。本文将重点介绍如何使用 Hibernate 和 JPA 构建 Criteria 查询。
2. Maven 依赖
为演示 API 用法,我们使用 JPA 参考实现 Hibernate。在 pom.xml
中添加最新版本依赖:
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.5.2.Final</version>
</dependency>
最新版本可在 Maven 仓库 获取。
3. Criteria 基础示例
先看如何使用 Criteria 查询获取数据。以获取数据库中所有 Item
实例为例:
public class Item implements Serializable {
private Integer itemId;
private String itemName;
private String itemDescription;
private Integer itemPrice;
// 标准的 setter 和 getter
}
获取所有 "ITEM" 表记录的简单查询:
Session session = HibernateUtil.getHibernateSession();
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Item> cr = cb.createQuery(Item.class);
Root<Item> root = cr.from(Item.class);
cr.select(root);
Query<Item> query = session.createQuery(cr);
List<Item> results = query.getResultList();
代码分步解析:
- 从
SessionFactory
创建Session
实例 - 通过
getCriteriaBuilder()
创建CriteriaBuilder
实例 - 调用
CriteriaBuilder
的createQuery()
创建CriteriaQuery
- 调用
Session
的createQuery()
创建Query
实例 - 执行
query
的getResultList()
获取结果
掌握基础后,我们探索 Criteria 查询的更多特性。
3.1. 使用表达式
CriteriaBuilder
可通过 CriteriaQuery.where()
和 Expressions
实现条件过滤。以下是常用表达式示例:
✅ 价格大于 1000 的商品:
cr.select(root).where(cb.gt(root.get("itemPrice"), 1000));
✅ 价格小于 1000 的商品:
cr.select(root).where(cb.lt(root.get("itemPrice"), 1000));
✅ 名称包含 "Chair" 的商品:
cr.select(root).where(cb.like(root.get("itemName"), "%chair%"));
✅ 价格在 100-200 之间的商品:
cr.select(root).where(cb.between(root.get("itemPrice"), 100, 200));
✅ 名称在指定列表中的商品:
cr.select(root).where(root.get("itemName").in("Skate Board", "Paint", "Glue"));
✅ 描述为 NULL 的商品:
cr.select(root).where(cb.isNull(root.get("itemDescription")));
✅ 描述非 NULL 的商品:
cr.select(root).where(cb.isNotNull(root.get("itemDescription")));
还可使用 isEmpty()
和 isNotEmpty()
检查集合属性。
Criteria API 支持链式组合表达式:
Predicate[] predicates = new Predicate[2];
predicates[0] = cb.isNull(root.get("itemDescription"));
predicates[1] = cb.like(root.get("itemName"), "chair%");
cr.select(root).where(predicates);
逻辑运算示例:
Predicate greaterThanPrice = cb.gt(root.get("itemPrice"), 1000);
Predicate chairItems = cb.like(root.get("itemName"), "Chair%");
✅ OR 逻辑组合:
cr.select(root).where(cb.or(greaterThanPrice, chairItems));
✅ AND 逻辑组合:
cr.select(root).where(cb.and(greaterThanPrice, chairItems));
3.2. 排序
Criteria 提供强大的排序功能。按名称升序、价格降序排列:
cr.orderBy(
cb.asc(root.get("itemName")),
cb.desc(root.get("itemPrice")));
3.3. 投影、聚合和分组函数
以下是常用聚合函数示例:
✅ 获取记录总数:
CriteriaQuery<Long> cr = cb.createQuery(Long.class);
Root<Item> root = cr.from(Item.class);
cr.select(cb.count(root));
Query<Long> query = session.createQuery(cr);
List<Long> itemProjected = query.getResultList();
✅ 计算平均价格:
CriteriaQuery<Double> cr = cb.createQuery(Double.class);
Root<Item> root = cr.from(Item.class);
cr.select(cb.avg(root.get("itemPrice")));
Query<Double> query = session.createQuery(cr);
List avgItemPriceList = query.getResultList();
其他实用聚合方法:sum()
, max()
, min()
, count()
等。
3.4. CriteriaUpdate
JPA 2.1 起支持使用 Criteria API 执行数据库更新。CriteriaUpdate
的 set()
方法可设置新值:
CriteriaUpdate<Item> criteriaUpdate = cb.createCriteriaUpdate(Item.class);
Root<Item> root = criteriaUpdate.from(Item.class);
criteriaUpdate.set("itemPrice", newPrice);
criteriaUpdate.where(cb.equal(root.get("itemPrice"), oldPrice));
Transaction transaction = session.beginTransaction();
session.createQuery(criteriaUpdate).executeUpdate();
transaction.commit();
更新多个属性只需多次调用 set()
方法。
3.5. CriteriaDelete
CriteriaDelete
支持通过 Criteria API 执行删除操作:
CriteriaDelete<Item> criteriaDelete = cb.createCriteriaDelete(Item.class);
Root<Item> root = criteriaDelete.from(Item.class);
criteriaDelete.where(cb.greaterThan(root.get("itemPrice"), targetPrice));
Transaction transaction = session.beginTransaction();
session.createQuery(criteriaDelete).executeUpdate();
transaction.commit();
3.6. 使用 CriteriaDefinition 工具
Hibernate 6.3.0 提供 CriteriaDefinition
类简化查询构建。它允许在匿名子类的初始化块中调用所有 CriteriaBuilder
和 CriteriaQuery
操作。
获取所有 "ITEM" 记录的示例:
final Session session = HibernateUtil.getHibernateSession();
final SessionFactory sessionFactory = HibernateUtil.getHibernateSessionFactory();
CriteriaDefinition<Item> query = new CriteriaDefinition<>(sessionFactory, Item.class) {
{
JpaRoot<Item> item = from(Item.class);
}
};
List<Item> items = session.createSelectionQuery(query).list();
常见查询示例:
✅ 价格大于 1000 的商品:
CriteriaDefinition<Item> query = new CriteriaDefinition<>(sessionFactory, Item.class) {
{
JpaRoot<Item> item = from(Item.class);
where(gt(item.get("itemPrice"), 1000));
}
};
List<Item> items = session.createSelectionQuery(query).list();
✅ 价格在 100-200 之间的商品:
where(between(item.get("itemPrice"), 100, 200));
通过 CriteriaDefinition
初始化块可轻松构建复杂查询。
4. 使用 JPA Criteria API 进行 Fetch Join
Fetch Join 操作在单次数据库查询中获取关联实体数据,避免 N+1 查询问题,提升性能。
4.1. 实体定义
定义 Player
实体:
@Entity
class Player {
@Id
private int id;
private String playerName;
private String teamName;
private int age;
@ManyToOne
@JoinColumn(name = "league_id")
private League league;
// 构造器、getter/setter、toString
}
League
实体:
@Entity
class League {
@Id
private int id;
private String name;
@OneToMany(mappedBy = "league")
private List<Player> players = new ArrayList<>();
// 构造器、getter/setter、toString
}
通过 league_id
外键关联两实体。
4.2. 执行 Fetch Join 操作
按联赛获取球员数据:
CriteriaQuery<Player> query = cb.createQuery(Player.class);
Root<Player> root = query.from(Player.class);
root.fetch("league", JoinType.LEFT);
query.where(cb.equal(root.get("league").get("name"), "Premier League"));
List<Player> playerList = session.createQuery(query).getResultList();
代码解析:
- 创建
Root
对象选择Player
实体 - 调用
fetch()
指定关联的league
字段 - 使用
JoinType.LEFT
执行左连接 - 查询英超联赛球员,单次查询同时获取
Player
和关联的League
数据
5. 相对于 HQL 的优势
前文展示了 Criteria 查询的用法。其相对于 HQL 的核心优势在于提供清晰、面向对象的 API。
优势总结:
- ✅ 构建更灵活的动态查询
- ✅ IDE 支持重构
- ✅ 享受 Java 类型安全特性
❌ 劣势:复杂关联查询可能不如 HQL 直观
建议根据场景选择工具——多数情况下 Criteria API 是优选,但特定场景可能需要更低阶方案。
6. 调用用户定义函数
JPA Criteria API 可通过 CriteriaBuilder.function()
直接调用数据库自定义函数。
假设数据库有计算折扣的函数:
CREATE ALIAS calculate_discount AS $$
double calculateDiscount(double itemPrice) {
double discountRate = 0.1;
return itemPrice - (itemPrice * discountRate);
}
$$;
在查询中调用该函数:
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Item> query = cb.createQuery(Item.class);
Root<Item> root = query.from(Item.class);
Expression<Double> discountPrice = cb.function("calculate_discount", Double.class, root.get("itemPrice"));
query.select(root).where(cb.lt(discountPrice, 500.0));
List<Item> items = session.createQuery(query).getResultList();
assertNotNull(items, "折扣商品不应为空");
assertFalse(items.isEmpty(), "应返回折扣商品");
assertEquals(3, items.size());
关键步骤:
- 创建
CriteriaQuery
- 使用
function()
调用自定义函数 - 指定函数名、返回类型和参数
- 将
itemPrice
作为参数传入 - 执行查询获取结果
实际开发中,调用自定义函数对处理复杂业务逻辑或数据库端计算特别有用。
7. 结论
本文介绍了 Hibernate 和 JPA 中 Criteria 查询的基础用法及高级特性。通过 Criteria API,开发者能以类型安全的方式构建动态查询,有效避免 SQL 注入风险,同时提升代码可维护性。
文中代码示例可在 GitHub 仓库 获取。