前言
在上一篇SpringBoot集成JPA及基本使用-CSDN博客,里面讲解了通过Spring Data JPA的命名规范实现数据库查询以及自定义SQL语句查询。而在开发中,不定个数的多条件查询是一种很常见的场景,如根据注册起止日期、用户名、用户级别等查询用户,且其中的条件并不是必须填写的。使用Criteria查询可以高效的解决以上问题。在开始讲解之前,先了解一下JPQL。
JPQL语言
JPQL语言(Java Persistence Query Language)是一种和SQL非常类似的中间性和对象化查询语言,它最终会被编译成具体的地场数据库的SQL语言,从而屏蔽不同的数据库的差异。
JPQL是面向对象进行查询的语言,开发者可以通过访问持久化映射的实体类,以及类中的属性来编写类似SQL的语句。
JPQL语言通过Query接口封装执行,在Query中封装了数据库访问操作的相关方法。
JPA元模型
在JPA中,标准的查询是以元模型的概念为基础的。元模型是以具体持久化单元的受管实体定义的,实体可以是实体类、嵌入式类或映射的父类。提供受管实体元信息的类就是元模型类。使用元模型最大优势是可以在编译时访问实体的持久属性。
如上一篇SpringBoot集成JPA及基本使用-CSDN博客中定义的ProductEntity实体类对应的元模型类的名称为ProductEntity_,类中的属性全部是使用publict和static修饰的,类型为SingularAttribute。如下:
package com.jingai.jpa.dao.entity;
import java.util.Date;
import javax.annotation.Generated;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;
@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(ProductEntity.class)
public abstract class ProductEntity_ {
public static volatile SingularAttribute<ProductEntity, Date> validateTime;
public static volatile SingularAttribute<ProductEntity, Date> createTime;
public static volatile SingularAttribute<ProductEntity, String> name;
public static volatile SingularAttribute<ProductEntity, String> deliveryNo;
public static volatile SingularAttribute<ProductEntity, String> securityCode;
public static volatile SingularAttribute<ProductEntity, Long> pid;
public static volatile SingularAttribute<ProductEntity, String> customer;
public static volatile SingularAttribute<ProductEntity, Integer> validateNum;
public static final String VALIDATE_TIME = "validateTime";
public static final String CREATE_TIME = "createTime";
public static final String NAME = "name";
public static final String DELIVERY_NO = "deliveryNo";
public static final String SECURITY_CODE = "securityCode";
public static final String PID = "pid";
public static final String CUSTOMER = "customer";
public static final String VALIDATE_NUM = "validateNum";
}
元模型并不需要手动创建,hibernate提供了自动生成的插件,只需引入依赖及相应的配置即可直动生成。
2.1 引入依赖
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<scope>provided</scope>
</dependency>
2.2 在Idea中配置
本人使用的是
此时会在项目的target/generated-sources/annotations目录中自动生成对应添加@Entity实体类对应的元模型类。
如果显示的格式有问题,导致在代码中无法访问元模型类,可以设置annotations为Source Root。
2.3 Source Root设置
对Source Root不了解的,可以看一下
IDEA新建文件时没有找到java类文件_idea新建java类不见了-CSDN博客
元模型自动生成的设置不止一种,具体见https://docs.jboss.org/hibernate/jpamodelgen/1.3/reference/en-US/html/chapter-usage.html
Criteria查询
还是以上一篇的Product为例,根据创建的起止日期、名称以及客户名进行查询。
3.1 创建一个通用的分页查询的表单
package com.jingai.jpa.common.form;
import org.springframework.util.StringUtils;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SearchBaseForm {
private static final DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd");
// 起始日期
private String startDate;
// 截止日期
private String endDate;
// 当前查询的页,从1开始
private Integer pageIndex;
// 每页的记录数
private Integer pageSize;
public Integer getPageIndex() {
return pageIndex == null || pageIndex == 0 ? 1 : pageIndex;
}
public Integer getPageSize() {
return pageSize == null || pageSize == 0 ? 20 : pageSize;
}
public Date getStartDate() {
try {
return StringUtils.hasText(startDate) ? FORMAT.parse(startDate) : null;
} catch(Exception e) {
e.printStackTrace();
}
return null;
}
public Date getEndDate() {
try {
return StringUtils.hasText(endDate) ? FORMAT.parse(endDate) : null;
} catch(Exception e) {
e.printStackTrace();
}
return null;
}
public void setEndDate(String endDate) {
this.endDate = endDate;
}
public void setPageIndex(Integer pageIndex) {
this.pageIndex = pageIndex;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public void setStartDate(String startDate) {
this.startDate = startDate;
}
}
3.2 创建Product的查询表单
package com.jingai.jpa.common.form;
import lombok.Data;
@Data
public class ProductForm extends SearchBaseForm {
private String name;
private String customer;
}
3.3 Repository的修改
在上一篇的ProductRepository继承了JpaRepository,而这里要改为继承JpaRepositoryImplementation,其他不变。其中JpaRepositoryImplementation继承了JpaRepository,还继承了JpaSpecificationExecutor。此处的例子中需要用到JpaSpecificationExecutor的findAll()接口。
package com.jingai.jpa.dao;
import com.jingai.jpa.dao.entity.ProductEntity;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
import java.util.List;
public interface ProductRepository extends JpaRepositoryImplementation<ProductEntity, Long> {
List<ProductEntity> findByPidBetween(long startPid, long endPid);
@Query("from ProductEntity where name like ?1")
List<ProductEntity> searchByName(String name);
}
3.4 Criteria分页查询
@Override
public Page<ProductEntity> listByPage(ProductForm form) {
// 创建一个Specification,实现接口中的toPredicate()方法,该方法返回一个Predicate
Specification<ProductEntity> specification = new Specification<ProductEntity>() {
@Override
public Predicate toPredicate(Root<ProductEntity> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>(8);
if(StringUtils.hasText(form.getName())) {
predicates.add(criteriaBuilder.like(root.get(ProductEntity_.NAME), "%" + form.getName() + "%"));
}
if(StringUtils.hasText(form.getCustomer())) {
predicates.add(criteriaBuilder.like(root.get(ProductEntity_.customer), "%" + form.getCustomer() + "%"));
}
if(form.getStartDate() != null) {
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get(ProductEntity_.createTime), form.getStartDate()));
}
if(form.getEndDate() != null) {
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get(ProductEntity_.createTime), form.getEndDate()));
}
return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
}
};
// 创建排序字段,可设置多个
Sort sort = Sort.by(Sort.Direction.DESC, ProductEntity_.createTime.getName());
Pageable pageable = PageRequest.of(form.getPageIndex(), form.getPageSize(), sort);
// 使用JpaSpecificationExecutor的findAll()方法,只能返回实体类的集合
return productRepository.findAll(specification, pageable);
}
Specification接口中的toPredicate()接收三个参数,分别为Root<T> root、CriteriaQuery<?> query、CriteriaBuilder criteriaBuilder。
CriteriaBuilder:用于构造条件查询、复合选择、表达式、谓词和排序
CriteriaQuery:定义了特定于顶级查询的功能,包含了查询的各个部分。如:select结果集、where条件、group by、order by等。在CriteriaQuery指定返回值结果集。
Root:定义Criteria查询的根对象,Criteria查询的根定义为实体类型,它与SQL查询中的FROM子句类似,定义了查询的FROM子句中能够出现的类型。可以有多个查询根对象
通过CriteriaBuilder提供的like、equal、lessThan、greaterThan等方法,返回Predicate对象,作为CriteriaQuery的where条件。
Predicate过滤条件应用到SQL语句的where子句中。在Criteria查询中,查询条件通过Predicate或Expression实例应用到CriteriaQuery对象上。
Predicate实例也可以使用Expression的isNull、isNotNull、in方法获取,复合Predicate语句可以使用CriteriaBuilder的and、or、andnot方法构建。
部分字段查询
在CriteriaQuery中,可以通过select()方法设置返回值集合,但使用JpaSpecificationExecutor的findAll()方法,只能返回实体类的集合,所以即使设置了也是返回ProductEntity实体。针对部分字段的查询,需要根据返回值信息,创建对应的CriteriaQuery对象。以下为查询部分字段的代码。
@Override
public Page<Object[]> listByPage2(ProductForm form) {
// 获取一个builder
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
// createQuery中的传参为搜索结果的返回值类型,也就是结果集的泛型为Object[]数组
CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
// 查询的根对象,可以有多个查询根对象
Root<ProductEntity> root = query.from(ProductEntity.class);
// 设置查询的返回值信息。此处只查询pid和name
query.multiselect(root.get(ProductEntity_.pid), root.get(ProductEntity_.name));
// 添加搜索条件Predicate
List<Predicate> predicates = new ArrayList<>(4);
if(StringUtils.hasText(form.getName())) {
predicates.add(builder.like(root.get(ProductEntity_.NAME), "%" + form.getName() + "%"));
}
if(StringUtils.hasText(form.getCustomer())) {
predicates.add(builder.like(root.get(ProductEntity_.customer), "%" + form.getCustomer() + "%"));
}
if(form.getStartDate() != null) {
predicates.add(builder.greaterThanOrEqualTo(root.get(ProductEntity_.createTime), form.getStartDate()));
}
if(form.getEndDate() != null) {
predicates.add(builder.lessThanOrEqualTo(root.get(ProductEntity_.createTime), form.getEndDate()));
}
Predicate[] predicateses = predicates.toArray(new Predicate[0]);
// 设置搜索条件
query.where(predicateses);
// 设置排序规则
query.orderBy(new OrderImpl(root.get(ProductEntity_.createTime), false));
// 执行分页查询
TypedQuery<Object[]> query1 = entityManager.createQuery(query);
query1.setFirstResult((form.getPageIndex()) * form.getPageSize());
query1.setMaxResults(form.getPageSize());
List<Object[]> list = query1.getResultList();
// 获取总记录数
// 设置新的查询返回值类型
CriteriaQuery<Long> queryCount = builder.createQuery(Long.class);
// 查询的根对象
root = queryCount.from(ProductEntity.class);
// 设置返回值信息,查询count(pid)
queryCount.select(builder.count(root.get(ProductEntity_.pid)));
// 设置查询条件
queryCount.where(predicateses);
Long count = entityManager.createQuery(queryCount).getSingleResult();
// 返回分页查询信息
Pageable pageable = PageRequest.of(form.getPageIndex(), form.getPageSize());
return new PageImpl<Object[]>(list, pageable, count);
}
针对分页查询,需要执行两次查询,一次是查询数据,另一次是查询总记录数。
结尾
Spring Data JPA的知识点还有很多,限于篇幅,本篇先分享到这里。
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。