71、Spring Data JPA 的 样本查询--参数作为样本去查询数据库的数据,也可以定义查询的匹配规则

★ 样本查询

给Spring Data传入一个样本数据,Spring Data就能从数据库中查询出和样本相同的数据。

被查询的数据并不需要和样本是完全相同的,可能只需要和样本有几个属性是相同的。

总结:
样本查询–就是把参数作为样本去查询数据库的数据,根据自定义的匹配规则,如果对应上就能把数据查出来
样本查询也可以定义其中查询的一些匹配规则

▲ 样本查询的API(QueryByExampleExecutor):

JpaRepository继承了QueryByExampleExecutor接口,该接口提供了如下“样本查询”方法:

-  Optional findOne(Example example);
-  Iterable findAll(Example example);
-  Iterable findAll(Example example, Sort sort);
-    Page   findAll(Example example, Pageable pageable);
-    long      count(Example example);
-    boolean   exists(Example example);

只要让DAO接口继承该QueryByExampleExecutor接口,DAO组件就可调用上面的样本查询方法。
——方法实现不要你操心,Spring Data会负责搞定它们。

▲ 创建Example

Example查询的关键在于Example参数,它提供了如下两个of()类方法来创建Example对象:

 - of(T probe):以probe对象创建最简单的Example对象,使用默认的匹配规则。
                要求被查询对象与样本对象所有属性都严格相等。

 - of(T probe, ExampleMatcher matcher):以probe创建Example对象,并使用matcher指定匹配规则。

▲ ExampleMatcher提供了如下静态方法来创建实例:

- static ExampleMatcher matching():创建一个需要所有属性都匹配的匹配器。 (and运算符)
- static ExampleMatcher matchingAll():完全等同于matching()方法
- static ExampleMatcher matchingAny():创建一个只要任意一个属性匹配的匹配器。(or运算符)

▲ ExampleMatcher还可通过如下方法来指定对特定属性的匹配规则:

- withIgnoreCase():指定属性匹配时默认不区分大小写。

- withIgnoreCase(String... propertyPaths):指定propertyPaths参数列出的属性在匹配时不区分大小写。

- withIgnoreNullValues():指定不比较Example对象中属性值为null的属性。

- withIgnorePaths(String... ignoredPaths):指定忽略ignoredPaths参数列出的属性,也就是这些属性不参与匹配。

- withIncludeNullValues():强行指定要比较Example对象中属性值为null的属性。

- withMatcher(String propertyPath, 比较器):对propertyPath参数指定的属性使用专门的匹配规则。

▲ 样本查询的步骤:

(1) 让DAO接口要么继承JpaRepository(是QueryByExampleExecutor的子接口),
      或者增加继承QueryByExampleExecutor

(2) DAO组件可调用QueryByExampleExecutor的样本查询方法

代码演示:

需求:根据一个对象中的某些属性作为样本,去数据库查询数据

要实现样本查询,
Dao接口需要继承 JpaRepository 接口,
或者增加继承 QueryByExampleExecutor 接口

接口可以继承接口,并且可以继承不只一个接口,但是不能实现接口。
因为接口的成员方法都具有抽象属性,不具有方法体,无法实现继承的接口

1、这样实现样本查询的接口继承
71、Spring Data JPA 的 样本查询--参数作为样本去查询数据库的数据,也可以定义查询的匹配规则_第1张图片
2、不需要在StudentDao组件写方法,或者写sql语句,直接在测试类进行查询方法的调用,因为DAO组件会调用QueryByExampleExecutor的样本查询方法,不用我们写。

DAO组件调用QueryByExampleExecutor的样本查询方法时,
查询的默认的匹配规则,要求被查询对象与样本对象所有属性都严格相等。

如图:
样本查询的条件都符合,但是没查出数据,是因为没有添加匹配规则,
而默认的匹配规则,要求被查询对象与样本对象所有属性都严格相等。
而上面只有student的两个属性,其他没写上去就会为null,
所以就查询对象与样本对象的所有属性没有全部相等。
所以查不出数据
没有指定匹配规则,所以要求样本对象(s) 和 查询对象(Student) 的所有属性都相等才行

测试1:没有添加匹配规则的测试
默认匹配规则:要求被查询对象与样本对象所有属性都严格相等
71、Spring Data JPA 的 样本查询--参数作为样本去查询数据库的数据,也可以定义查询的匹配规则_第2张图片

测试2:添加匹配规则的测试
添加匹配规则:不比较 Null 值
71、Spring Data JPA 的 样本查询--参数作为样本去查询数据库的数据,也可以定义查询的匹配规则_第3张图片

解释:.withIgnorePaths (“id”, “gender”)
id 是 Integer 类型,默认值是null , 所以也可以不排除
基本类型的属性,如果不作为参数去查询,就需要排除出去
71、Spring Data JPA 的 样本查询--参数作为样本去查询数据库的数据,也可以定义查询的匹配规则_第4张图片

测试3:添加匹配规则的测试
添加匹配规则:调用 matchingAny() ,相当于 OR 运算符,只要有一个参数和查询的student对象对应得上,就能将该对象查出来
71、Spring Data JPA 的 样本查询--参数作为样本去查询数据库的数据,也可以定义查询的匹配规则_第5张图片

测试4:添加匹配规则的测试
添加的匹配规则:.withMatcher()
作用:
指定自己的匹配规则,指定要求以样本属性值作为结尾即可。
相当于查询 address like "%洞"就可以了,而不是 address = “洞”

需求:查询地址匹配指定后缀的student

71、Spring Data JPA 的 样本查询--参数作为样本去查询数据库的数据,也可以定义查询的匹配规则_第6张图片

匹配规则方法: .withMatcher() ,还能这样写,更灵活
.withMatcher(“address”,matcher -> matcher.endsWith())
也是以该样本属性作为后缀进行查询
71、Spring Data JPA 的 样本查询--参数作为样本去查询数据库的数据,也可以定义查询的匹配规则_第7张图片

完整代码

StudentDao

这个接口只需要再继承 QueryByExampleExecutor 这个接口就可以了,
或者直接继承 JpaRepository 接口也可以
在这里插入图片描述
不需要在StudentDao组件写方法,或者写sql语句,直接在测试类进行查询方法的调用,因为DAO组件会调用QueryByExampleExecutor的样本查询方法,不用我们写。

package cn.ljh.app.dao;

import cn.ljh.app.domain.Student;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;

import java.util.List;

//CrudRepository 的第一个泛型参数是被操作的实体类型,第二个参数是实体的主键类型
//public interface StudentDao extends CrudRepository

//如果要做样本查询,Dao接口需要继承 JpaRepository 接口,
//public interface StudentDao extends JpaRepository

//或者增加继承 QueryByExampleExecutor 接口
public interface StudentDao extends CrudRepository<Student,Integer> , QueryByExampleExecutor<Student>
{
    //查询年龄大于指定参数的学生
    List<Student> findByAgeGreaterThan(int startAge);

    //根据年龄和班级名称查询学生
    //Age 和 ClazzName 用 And 连接起来,表示两个查询条件,
    //ClazzName这两个单词中间没有And连接起来,表示是一个路径写法,表示是Clazz类的name属性
    List<Student> findByAgeAndClazzName(int age , String clazzName);

    //根据地址后缀进行分页查询,查询 address 带有 "洞" 的学生并进行分页
    Page<Student> findByAddressEndingWith(String addrSuffix, Pageable pageable);
}

StudentDaoTest

package cn.ljh.app.dao;


import cn.ljh.app.domain.Student;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.*;

import java.util.List;

//SpringBootTest.WebEnvironment.NONE : 表示不需要web环境
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class StudentDaoTest
{
    @Autowired
    private StudentDao studentDao;

    /**
     * @ValueSource: 每次只能传一个参数
     * @CsvSource:每次可以传多个参数
     */

    //需求:查询年龄大于指定参数的记录
    //参数化测试
    @ParameterizedTest
    @ValueSource(ints = {20, 200})
    public void testFindByAgeGreaterThan(int startAge)
    {
        List<Student> students = studentDao.findByAgeGreaterThan(startAge);
        students.forEach(System.err::println);
    }

    //根据年龄和班级名称查询学生
    //Age 和 ClazzName 用 And 连接起来,表示两个查询条件,
    //ClazzName这两个单词中间没有And连接起来,表示是一个路径写法,表示是Clazz类的name属性
    @ParameterizedTest
    //参数一个是int,一个是String,这个注解在传参的时候会自动进行类型转换
    @CsvSource(value = {"20,超级A营", "18,超级D班"})
    public void testFindByAgeAndClazzName(int age, String clazzName)
    {
        List<Student> students = studentDao.findByAgeAndClazzName(age, clazzName);
        students.forEach(System.err::println);
    }


    //pageNo: 要查询哪一页的页数 , pageSize: 每页显示的条数
    @ParameterizedTest
    @CsvSource({"洞,2,3", "洞,1,4", "洞,3,2"})
    public void testFindByAddressEndingWith(String addrSuffix, int pageNo, int pageSize)
    {
        //分页对象,此处的pageNo是从0开始的,0代表第一页,所以这里的 pageNo 要 -1
        Pageable pageable1 = PageRequest.of(pageNo - 1, pageSize);
        Page<Student> students = studentDao.findByAddressEndingWith(addrSuffix, pageable1);

        int number = students.getNumber() + 1;
        System.err.println("总页数:" + students.getTotalPages());
        System.err.println("总条数:" + students.getTotalElements());
        System.err.println("当前第:" + number + " 页");
        System.err.println("当前页有:" + students.getNumberOfElements() + " 条数据");
        students.forEach(System.err::println);
    }

    //==============测试样本查询从这里开始=====================================================
    
    //根据一个对象中的某些属性作为样本,去数据库查询数据
    @ParameterizedTest
    @CsvSource({"孙悟空,500", "猪八戒,200"})
    public void exampleTest1(String name, int age)
    {
        Student s = new Student();
        s.setName(name);
        s.setAge(age);
        //没有指定匹配规则,所以要求样本对象(s) 和 查询对象(Student) 的所有属性都相等才行
        Iterable<Student> students = studentDao.findAll(Example.of(s));
        students.forEach(System.err::println);
    }

    //根据一个对象中的某些属性作为样本,去数据库查询数据
    @ParameterizedTest
    @CsvSource({"孙悟空,500", "猪八戒,200"})
    public void exampleTest2(String name, int age)
    {
        Student s = new Student();
        s.setName(name);
        s.setAge(age);
        Iterable<Student> students = studentDao.findAll(Example.of(s,
                //添加匹配规则
                ExampleMatcher.matching()
                        //不比较Null值
                        .withIgnoreNullValues()
                        //不比较id、gender,因为gender是基本类型,有默认值,所以需要排除在外
                        .withIgnorePaths("gender")
        ));
        students.forEach(System.err::println);
    }


    //根据一个对象中的某些属性作为样本,去数据库查询数据
    @ParameterizedTest
    @CsvSource({"孙悟空,99999", "猪八戒,200"})
    public void exampleTest3(String name, int age)
    {
        Student s = new Student();
        s.setName(name);
        s.setAge(age);
        Iterable<Student> students = studentDao.findAll(Example.of(s,
                //添加匹配规则:Or 运算符,只要有一个属性对得上就查出来
                ExampleMatcher.matchingAny()
                        //不比较Null值
                        .withIgnoreNullValues()
                        //不比较id、gender
                        .withIgnorePaths("id", "gender")
        ));
        students.forEach(System.err::println);
    }

    //查询地址匹配指定后缀的student
    @ParameterizedTest
    @ValueSource(strings = {"洞", "河"})
    public void exampleTest4(String addressSuffix)
    {
        Student s = new Student();
        s.setAddress(addressSuffix);
        Iterable<Student> students = studentDao.findAll(Example.of(s,
                //添加匹配规则:Or 运算符,只要有一个属性对得上就查出来
                ExampleMatcher.matchingAny()
                        //不比较Null值
                        .withIgnoreNullValues()
                        //如果参数查询没用到 gender , age 这些基本类型,则排除在外不进行比较,因为有默认值
                        .withIgnorePaths("gender", "age")
                        //指定自己的匹配规则,指定要求以样本属性值作为结尾即可。
                        //相当于查询 address like "%洞"就可以了,而不是 address = "洞"
                        .withMatcher("address",
                                ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.ENDING))
        ));
        students.forEach(System.err::println);
    }

    //查询地址匹配指定后缀的student
    @ParameterizedTest
    @ValueSource(strings = {"洞", "河"})
    public void exampleTest5(String addressSuffix)
    {
        Student s = new Student();
        s.setAddress(addressSuffix);
        Iterable<Student> students = studentDao.findAll(Example.of(s,
                //添加匹配规则:Or 运算符,只要有一个属性对得上就查出来
                ExampleMatcher.matchingAny()
                        //不比较Null值
                        .withIgnoreNullValues()
                        //如果参数查询没用到 gender , age 这些基本类型,则排除在外不进行比较,因为有默认值
                        .withIgnorePaths("gender", "age")
                        //指定自己的匹配规则,指定要求以样本属性值作为结尾即可。
                        //相当于查询 address like "%洞"就可以了,而不是 address = "洞"
                        //更灵活的写法matcher -> matcher.endsWith()
                        .withMatcher("address",matcher -> matcher.endsWith())
        ));
        students.forEach(System.err::println);
    }

}

你可能感兴趣的:(springboot,Spring,Data,JPA)