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();

代码分步解析:

  1. SessionFactory 创建 Session 实例
  2. 通过 getCriteriaBuilder() 创建 CriteriaBuilder 实例
  3. 调用 CriteriaBuildercreateQuery() 创建 CriteriaQuery
  4. 调用 SessioncreateQuery() 创建 Query 实例
  5. 执行 querygetResultList() 获取结果

掌握基础后,我们探索 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 执行数据库更新CriteriaUpdateset() 方法可设置新值:

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 类简化查询构建。它允许在匿名子类的初始化块中调用所有 CriteriaBuilderCriteriaQuery 操作

获取所有 "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();

代码解析:

  1. 创建 Root 对象选择 Player 实体
  2. 调用 fetch() 指定关联的 league 字段
  3. 使用 JoinType.LEFT 执行左连接
  4. 查询英超联赛球员,单次查询同时获取 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());

关键步骤:

  1. 创建 CriteriaQuery
  2. 使用 function() 调用自定义函数
  3. 指定函数名、返回类型和参数
  4. itemPrice 作为参数传入
  5. 执行查询获取结果

实际开发中,调用自定义函数对处理复杂业务逻辑或数据库端计算特别有用

7. 结论

本文介绍了 Hibernate 和 JPA 中 Criteria 查询的基础用法及高级特性。通过 Criteria API,开发者能以类型安全的方式构建动态查询,有效避免 SQL 注入风险,同时提升代码可维护性。

文中代码示例可在 GitHub 仓库 获取。


原始标题:JPA Criteria Queries | Baeldung