1. 引言
Java Persistence API (JPA) 是广泛使用的规范,用于在Java对象和关系型数据库之间访问、持久化和数据管理。JPA应用中常见的需求是统计满足特定条件的实体数量。使用JPA提供的*CriteriaQuery* API可以高效完成这项任务。
Criteria Query的核心组件是CriteriaBuilder和CriteriaQuery接口。CriteriaBuilder是创建各种查询元素(如谓词、表达式和条件查询)的工厂。而CriteriaQuery则代表封装了选择、过滤和排序条件的查询对象。
本文将深入探讨JPA中的计数查询,展示如何利用Criteria Query API轻松高效地执行计数操作。我们将快速概述CriteriaQuery及其生成计数查询的方法,然后通过图书馆管理系统的实际案例,演示如何在不同场景下使用CriteriaQuery API统计图书数量。
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>
依赖配置完成后,我们构建一个简单的图书馆管理系统示例。该系统支持各种查询操作,如统计所有图书、按特定作者/标题/年份统计以及组合条件统计。首先定义包含title、author、category和year字段的Book实体:
@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
}
接下来创建仓库接口,用于执行针对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统计实体数量
计数查询常用于确定满足特定条件的实体总数。使用CriteriaQuery可以简单高效地构建计数查询。下面分步说明如何在JPA中执行计数查询。
3.1. 初始化CriteriaBuilder和CriteriaQuery
构建计数查询的第一步是从*EntityManager获取CriteriaBuilder*实例。该对象是创建查询元素的入口点:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
该对象用于构建查询的各个部分,包括条件查询、表达式、谓词和选择项。接下来创建条件查询:
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
这里指定查询结果类型为Long,表明查询将返回计数值。
3.2. 创建Root并选择计数
接下来创建Root对象,表示要执行计数操作的实体。然后使用CriteriaBuilder基于该对象构建计数表达式:
Root<Book> bookRoot = cq.from(Book.class);
cq.select(cb.count(bookRoot));
首先定义查询根,指定查询基于Book实体。然后使用CriteriaBuilder提供的cb.count()创建计数表达式。**count方法统计查询结果中的行数,它接收表达式(此处为bookRoot)作为参数,返回表示匹配条件的行数的表达式**。
最后,cq.select()将查询结果设置为该计数表达式。本质上,这告诉查询最终结果应为匹配指定条件的Book实体数量(条件将在后续部分介绍)。
3.3. 执行查询
CriteriaQuery构建完成后,使用EntityManager执行查询:
Long count = entityManager.createQuery(cq).getSingleResult();
*这里使用entityManager.createQuery(cq)从CriteriaQuery创建TypedQuery,并通过getSingleResult()*获取计数值作为单个结果**。
4. 处理条件与约束
实际场景中,计数查询通常需要基于特定条件进行过滤。Criteria Query提供了使用谓词为查询添加条件的灵活机制。
下面深入探讨如何利用多个条件实现计数查询。假设要统计标题包含特定关键词的图书数量:
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();
}
除前述步骤外,条件计数需要创建Predicate表示SQL查询的WHERE子句。
cb.like方法创建检查图书标题是否包含关键词的条件。%是匹配任意字符序列的通配符。通过*cq.where(condition)*将谓词添加到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();
}
此处谓词基于*cb.equal()*方法,仅过滤包含精确作者名的记录。
5. 组合多个条件
Criteria Query允许使用AND、OR和NOT等逻辑运算符组合多个条件。考虑以下基于多个条件统计图书数量的场景。假设要统计特定作者、标题和出版年份的图书数量:
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();
}
*这里创建两个分别表示作者和出版年份条件的谓词,然后使用cb.or()*组合这些谓词形成复合条件**。
类似地,可能需要统计满足以下任一条件的图书数量:标题包含关键词,或同时满足作者和年份条件:
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();
}
*我们创建了三个谓词,现在需要使用cb.or(authorAndYear, titleCondition)在authorAndYearCondition和titleCondition*之间建立OR关系**。
6. 集成测试
下面提供集成测试模式,确保计数查询正常工作。使用Spring提供的*@DataJPATest注解,在测试中注入必要的仓库层,底层使用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);
}
类似地,可以为仓库中提供的所有计数场景编写测试用例。
7. 结论
本文演示了如何在Spring Boot应用中使用JPA Criteria API执行条件计数。我们搭建了基础的图书馆管理系统,实现了基于特定条件统计图书数量的自定义仓库方法。
本文的完整实现可在GitHub上获取。