在 JPA 中使用 CriteriaQuery 执行 COUNT 查询
1、简介
Java Persistence API(JPA)是一种广泛使用的规范,用于访问、持久化和管理 Java 对象与关系数据库之间的数据。JPA 应用中的一项常见任务是计算符合特定条件的实体数量。使用 JPA 提供的 CriteriaQuery
API 可以高效地完成这项任务。
CriteriaQuery
的核心组件是 CriteriaBuilder
和 CriteriaQuery
接口。CriteriaBuilder
是创建各种查询元素(如 Predicate
、表达式和 CriteriaQuery
)的工厂。而,CriteriaQuery
代表一个查询对象,它封装了 select
、filter
和 order
标准。
本文将带你了解 JPA 中的 COUNT
查询,学习如何利用 CriteriaQuery
API 轻松高效地执行 COUNT 操作。
本文以一个简单的图书管理系统为例,介绍如何利用 CriteriaQuery
API 生成各种场景下的图书 COUNT
查询。
2、依赖
创建示例项目。
添加所需的 maven 依赖,包括 spring-data-jpa、spring-boot-starter-test 和 h2 内存数据库:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
依赖添加完成后,创建图书管理系统示例。它允许我们执行各种查询,如统计所有图书,统计某个作者、书名和年份的图书的各种组合。
添加一个 Book
(图书)实体,包含了 title
、author
、category
和 year
字段:
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String Category;
private String author;
private int year;
// 构造函数、Getter/Setter 方法省略
}
创建其 repository
接口,可以对 Book
实体进行各种操作:
public interface BookRepositoryCustom {
long countAllBooks();
long countBooksByTitle(String title);
long countBooksByAuthor(String author);
long countBooksByCategory(String category);
long countBooksByTitleAndAuthor(String title, String author);
long countBooksByAuthorOrYear(String author, int year);
}
3、使用 CriteriaQuery 计算实体数量
COUNT 查询通常用于确定满足特定条件的实体总数。使用 CriteriaQuery
,我们可以直接、高效地构建 COUNT 查询。
3.1、初始化 CriteriaBuilder 和 CriteriaQuery
要构建 COUNT 查询,首先需要从 EntityManager
获取 CriteriaBuilder
的实例。
CriteriaBuilder
是创建查询元素的入口点:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
该对象用于构建查询的不同部分,如条件查询、表达式、Predicate 和 select。接下来,用它创建一个 CriteriaQuery:
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
如上,将查询的结果类型指定为 Long
,表示希望查询返回一个 count 值。
3.2、创建 Root 和 Count 表达式
接下来,创建一个 Root
对象,代表要执行 count 操作的实体。然后,使用 CriteriaBuilder
根据该根对象构建一个 count 表达式:
Root<Book> bookRoot = cq.from(Book.class);
cq.select(cb.count(bookRoot));
如上,首先定义查询的 root
对象,指定查询基于 Book
实体。接下来,使用 CriteriaBuilder
提供的 cb.count()
创建一个 count 表达式。count 方法计算查询结果中的行数。它将表达式(本例中为 bookRoot
)作为参数,并返回一个表达式,该表达式表示符合查询中定义的条件的行数。
最后,cq.select()
会将查询结果设置为这个 count 表达式。本质上,它告诉查询,最终结果应该是符合指定条件的 Book
实体的数量。
3.3、执行查询
构建好了 CriteriaQuery
后,就可以使用 EntityManager
执行查询:
Long count = entityManager.createQuery(cq).getSingleResult();
如上,使用 entityManager.createQuery(cq)
从 CriteriaQuery
创建一个 TypedQuery
,并使用 getSingleResult()
以单个结果的形式检 COUNT 值。
4、处理 Criteria 和条件
在实际中,COUNT 查询经常需要根据某些 Criteria 或条件进行过滤。Criteria 查询提供了一种灵活的机制,可使用 Predicate 为查询添加 Criteria。
接下来看看如何利用多重条件来生成 COUNT 查询。假设我们想查询标题中包含特定关键词的所有书籍的数量:
long countBooksByTitle(String titleKeyword) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
Root<Book> bookRoot = cq.from(Book.class);
Predicate condition = cb.like(bookRoot.get("title"), "%" + titleKeyword + "%");
cq.where(condition);
cq.select(cb.count(bookRoot));
return entityManager.createQuery(cq).getSingleResult()
}
除了前面的步骤,还为 COUNT 查询创建了一个 Predicate,代表 SQL 查询的 WHERE
子句。
cb.like
方法会创建一个条件,检查书名是否包含 titleKeyword
。%
是通配符,可以匹配任何字符序列。然后,使用 cq.where(condition)
将此 Predicate 添加到 CriteriaQuery
中,从而将此条件应用到查询中。
另一个场景是获取某个作者所有书籍的数量:
long countBooksByAuthor(String authorName) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
Root<Book> bookRoot = cq.from(Book.class);
Predicate condition = cb.equal(bookRoot.get("author"), authorName);
cq.where(condition);
cq.select(cb.count(bookRoot));
return entityManager.createQuery(cq).getSingleResult();
}
如上,Predicate 基于 cb.equal()
方法,该方法只过滤包含确切 authorName
的记录。
5、组合多个 Criteria
Criteria
查询允许我们使用 AND
、OR
和 NOT
等逻辑运算符组合多个 Criteria
。
考虑一下根据多个条件计算图书数量的情况,假设,我们想获得包含特定作者、书名和出版年份的所有图书数量:
long countBooksByAuthorOrYear(int publishYear, String authorName) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
Root<Book> bookRoot = cq.from(Book.class);
Predicate authorCondition = cb.equal(bookRoot.get("author"), authorName);
Predicate yearCondition = cb.greaterThanOrEqualTo(bookRoot.get("publishYear"), 1800);
cq.where(cb.or(authorCondition, yearCondition));
cq.select(cb.count(bookRoot));
return entityManager.createQuery(cq).getSingleResult();
}
如上,创建了两个 Predicate
,分别代表图书作者和出版年份的条件。然后,使用 cb.and()
将这些谓词组合起来,形成一个复合条件。
同样,也可以在某种情况下获取具有特定书名或具有作者和年份组合的图书的数量:
long countBooksByTitleOrYearAndAuthor(String authorName, int publishYear, String titleKeyword) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
Root<Book> bookRoot = cq.from(Book.class);
Predicate authorCondition = cb.equal(bookRoot.get("author"), authorName);
Predicate yearCondition = cb.equal(bookRoot.get("publishYear"), publishYear);
Predicate titleCondition = cb.like(bookRoot.get("title"), "%" + titleKeyword + "%");
Predicate authorAndYear = cb.and(authorCondition, yearCondition);
cq.where(cb.or(authorAndYear, titleCondition));
cq.select(cb.count(bookRoot));
return entityManager.createQuery(cq).getSingleResult();
}
如上,再次创建了三个 Predicate
,但在 authorAndYearCondition
Predicate 和 titleCondition
Predicate 之间使用 cb.or(authorAndYear, titleCondition)
进行或运算。
6、集成测试
现在,使用 Spring 提供的 @DataJPATest
注解,在测试中注入必要的 Repository 层,将内存数据库中的 H2 用作底层持久化存储。在测试类中注入 TestEntityManager
,并用它来插入数据。
以获取某个作者的所有书籍的数量为例:
@Test
void givenBookDataAdded_whenCountBooksByAuthor_thenReturnsCount() {
entityManager.persist(new Book("Java Book 1", "Author 1", 1967, "Non Fiction"));
entityManager.persist(new Book("Java Book 2", "Author 1", 1999, "Non Fiction"));
entityManager.persist(new Book("Spring Book", "Author 2", 2007, "Non Fiction"));
long count = bookRepository.countBooksByAuthor("Author 1");
assertEquals(2, count);
}
与上例类似,我们可以为 repository 中提供的所有不同 COUNT 查询场景编写测试。
7、总结
本文介绍了如何在 Spring Boot 应用中使用 JPA Criteria API 执行 COUNT 查询。
Ref:https://www.baeldung.com/jpa-criteriaquery-count-queries