12、Spring Data JPA 实战 - Specifications动态查询:概述

1. 概述

当给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。
相比JPQL,其优势是类型安全,更加的面向对象。

在其源码里面,定义了许多方法,与JpaRepository接口里面的方法差不多

JpaSpecificationExecutor里面的查询方法的参数是Specification< T> spec;由此可以动态去拼接不同的查询条件以调用方法的显示去完成需求

2. JpaSpecificationExecutor方法介绍

T findOne(Specification<T> spec);  //查询单个对象

List<T> findAll(Specification<T> spec);  //查询列表

//查询全部,分页
//pageable:分页参数
//返回值:分页pageBean(page:是springdatajpa提供的)
Page<T> findAll(Specification<T> spec, Pageable pageable);

//查询列表
//Sort:排序参数
List<T> findAll(Specification<T> spec, Sort sort);

long count(Specification<T> spec);//统计查询

Specification :查询条件,这是一个接口

  • 需要自定义我们自己的Specification实现类

  • 实现方法:

  • Predicate toPredicate(Root root, CriteriaQuery<?> query, CriteriaBuilder cb); //用于封装查询条件

  • root:查询的根对象(查询的任何属性都可以从根对象中获取)

  • CriteriaQuery:顶层查询对象,自定义查询方式(作为一个了解,一般不用)

  • CriteriaBuilder:查询的构造器,封装了很多的查询条件

3. 测试动态查询

3.1 测试环境搭建

该项目的测试环境与之前(SpringDataJPA(6)SpringDataJPA概述与使用SpringDataJPA进行CRUD操作)的一样,在这里便不再继续叙述了
在这里的dao层的内容如下:

package cn.yy.dao;

import cn.yy.domain.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

/**
 * @author DDKK.COM 弟弟快看,程序员编程资料站
 * @date 2022/12/5
 *
 *      符合SpringDataJpa的dao层接口规范
 *              JpaRepository<操作的实体类类型,实体类中主键属性的类型>
 *                  封装了基本CRUD操作”
 *              JpaSpecificationExecutor<操作的实体类类型>
 *                  封装了复杂查询(分页)
 */
public interface SpecCustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
   
     
    
}

3.2 查询单个对象

测试类中的代码:

package cn.yy.test;

import cn.yy.dao.CustomerDao;
import cn.yy.dao.SpecCustomerDao;
import cn.yy.domain.Customer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.*;
import java.util.Arrays;
import java.util.List;

/**
 * @author DDKK.COM 弟弟快看,程序员编程资料站
 * @date 2022/12/5
 */
@RunWith(SpringJUnit4ClassRunner.class)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class SpecTest {
   
     
    @Autowired
    private SpecCustomerDao specCustomerDao;

    /**
     * 根据条件查询单个对象:
     *      根据客户名查询:
     *          查询条件:
     *              1.查询方式
     *                  cb对象
     *              2.比较的属性名称
     *                  root对象
     */
    @Test
    public void testSpec(){
   
     
        /*匿名内部类,自定义查询条件
            1.实现Specification接口(提供泛型:查询的对象类型)
            2.实现toPredicate方法(构造查询条件)
            3.需要借助方法参数中的两个参数(
                    root:获取需要查询的对象属性
                    CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配。)
               )
         */
        Specification<Customer> spec = new Specification<Customer>() {
   
     
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
   
     
                //1.获取比较的属性:参数是属性名
                Path<Object> custName = root.get("custName");
                //2.构造查询条件:select * from cst_customer where cust_name = '更新后的结果'
                /*
                        第一个参数:需要比较的属性(path对象)
                        第二个参数:当前需要比较的取值
                 */
                Predicate predicate = criteriaBuilder.equal(custName, "更新后的结果");//进行精准的匹配(比较的属性,比较的是属性的取值)
                return predicate;
            }
        };
        Customer one = specCustomerDao.findOne(spec);
        System.out.println(one);
    }

}

运行结果:

 

3.3 完成多条件拼接

测试类中的代码,该测试是在3.2中的测试类中运行的:

/**
 * 多条件查询:
 *      根据客户名和客户所属行业进行查询
 */
@Test
public void testSpec1(){

 
    /*
    root:获取属性
        客户名
        所属行业
     cb:构造查询
        1.构造客户名的精准匹配查询
        2.构造所属行业的精准匹配查询
        3.将以上两个查询联系起来
     */
    Specification<Customer> spec = new Specification<Customer>() {

 
        @Override
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {

 
            Path<Object> custName = root.get("custName");//客户名
            Path<Object> custIndustry = root.get("custIndustry");//所属行业
            //构造查询
            //1.构造客户名的精准匹配查询
            Predicate p1 = criteriaBuilder.equal(custName, "延迟");
            //2.构造所属行业的精准匹配查询
            Predicate p2 = criteriaBuilder.equal(custIndustry, "学习");
            //3.将以上两个查询联系起来(组合在一起):and、or
            Predicate and = criteriaBuilder.and(p1, p2);//and():以与的形式拼接多个查询条件;cb.or():以或的形式拼接多个查询条件

            return and;
        }
    };
    //是使用findOne还是findAll是要根据数据库中数据的内容而定
    Customer one = specCustomerDao.findOne(spec);
    System.out.println(one);
}

运行结果:

 

3.4 模糊匹配查询列表

测试类中的代码,该测试是在3.2中的测试类中运行的:

/**
 * 根据客户名称的模糊匹配
 *      equal():直接的到path对象(属性),然后进行比较即可
 *      gt、lt、ge、le、like:这些方法不能直接去比较,
 *                          需要先得到path对象,根据path指定比较的参数类型,再去进行比较
 *                          指定参数类型:path.as(类型的字节码对象)
 */
@Test
public void testSpec3(){

 
    //构造查询条件
    Specification<Customer> spec = new Specification<Customer>() {

 
        @Override
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {

 
            //查询属性:客户名
            Path<Object> custName = root.get("custName");
            //查询方式:模糊查询
            Predicate predicate = criteriaBuilder.like(custName.as(String.class), "延%");
            return predicate;
        }
    };
    List<Customer> all = specCustomerDao.findAll(spec);
    for (Customer customer :all){

 
        System.out.println(customer);
    }
}

运行结果:

 

3.5 排序

测试类中的代码,该测试是在3.2中的测试类中运行的:

/**
 * 根据客户名称的模糊匹配
 *      equal():直接的到path对象(属性),然后进行比较即可
 *      gt、lt、ge、le、like:这些方法不能直接去比较,
 *                          需要先得到path对象,根据path指定比较的参数类型,再去进行比较
 *                          指定参数类型:path.as(类型的字节码对象)
 */
@Test
public void testSpec3(){

 
    //构造查询条件
    Specification<Customer> spec = new Specification<Customer>() {

 
        @Override
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {

 
            //查询属性:客户名
            Path<Object> custName = root.get("custName");
            //查询方式:模糊查询
            Predicate predicate = criteriaBuilder.like(custName.as(String.class), "延%");
            return predicate;
        }
};
    /*创建排序对象,需要调用构造方法实例化Sort对象
            第一个参数:排序的顺序(倒序,正序)
                Sort.Direction.DESC:倒序
                Sort.Direction.ASC:升序
            第二个参数:排序的属性名称
     */
    Sort sort = new Sort(Sort.Direction.DESC,"custId");
    List<Customer> all = specCustomerDao.findAll(spec,sort);
    for (Customer customer :all){

 
        System.out.println(customer);
    }
}

运行结果:

 

3.6 分页

测试类中的代码,该测试是在3.2中的测试类中运行的:

/**
 * 分页查询:
 *      findAll(Specification, Pageable):带有条件的分页
 *          Specification:查询条件
 *          Pageable:分页参数
 *              分页参数: 查询的页码,每页查询的条数
 *      findAll(Pageable):没有条件的分页
 *
 *      返回: Page对象: (这个对象是springDataJpa为我们封装好的pageBean对象,包括:数据列表、共条数)
 */
@Test
public void testSpec4(){

 
    Specification<Customer> spec = null;
    /*
    创建PageRequest的过程中,需要调用他的构造方法传入两个参数
            第一个参数:当前查询的页数(从0开始)
            第二个参数:每页查询的数量
     */
    Pageable pageable = new PageRequest(0,2);
    Page<Customer> all = specCustomerDao.findAll(spec, pageable);
    System.out.println("数据列表集合:"+all.getContent());
    System.out.println("得到总条数:"+all.getTotalElements());
    System.out.println("得到总页数:"+all.getTotalPages());
}

运行结果: