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


    ★ 样本查询

    给Spring Data传入一个样本数据,Spring Data就能从数据库中查询出和样本相同的数据。
    
    被查询的数据并不需要和样本是完全相同的,可能只需要和样本有几个属性是相同的。
    
    • 1
    • 2
    • 3

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

    ▲ 样本查询的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会负责搞定它们。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ▲ 创建Example

    Example查询的关键在于Example参数,它提供了如下两个of()类方法来创建Example对象:
    
     - of(T probe):以probe对象创建最简单的Example对象,使用默认的匹配规则。
                    要求被查询对象与样本对象所有属性都严格相等。
    
     - of(T probe, ExampleMatcher matcher):以probe创建Example对象,并使用matcher指定匹配规则。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

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

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

    - withIgnoreCase():指定属性匹配时默认不区分大小写。
    
    - withIgnoreCase(String... propertyPaths):指定propertyPaths参数列出的属性在匹配时不区分大小写。
    
    - withIgnoreNullValues():指定不比较Example对象中属性值为null的属性。
    
    - withIgnorePaths(String... ignoredPaths):指定忽略ignoredPaths参数列出的属性,也就是这些属性不参与匹配。
    
    - withIncludeNullValues():强行指定要比较Example对象中属性值为null的属性。
    
    - withMatcher(String propertyPath, 比较器):对propertyPath参数指定的属性使用专门的匹配规则。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ▲ 样本查询的步骤:

    (1) 让DAO接口要么继承JpaRepository(是QueryByExampleExecutor的子接口),
          或者增加继承QueryByExampleExecutor
    
    (2) DAO组件可调用QueryByExampleExecutor的样本查询方法
    
    • 1
    • 2
    • 3
    • 4

    代码演示:

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

    要实现样本查询,
    Dao接口需要继承 JpaRepository 接口,
    或者增加继承 QueryByExampleExecutor 接口
    
    接口可以继承接口,并且可以继承不只一个接口,但是不能实现接口。
    因为接口的成员方法都具有抽象属性,不具有方法体,无法实现继承的接口
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1、这样实现样本查询的接口继承
    在这里插入图片描述
    2、不需要在StudentDao组件写方法,或者写sql语句,直接在测试类进行查询方法的调用,因为DAO组件会调用QueryByExampleExecutor的样本查询方法,不用我们写。

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

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

    测试1:没有添加匹配规则的测试
    默认匹配规则:要求被查询对象与样本对象所有属性都严格相等
    在这里插入图片描述

    测试2:添加匹配规则的测试
    添加匹配规则:不比较 Null 值
    在这里插入图片描述

    解释:.withIgnorePaths (“id”, “gender”)
    id 是 Integer 类型,默认值是null , 所以也可以不排除
    基本类型的属性,如果不作为参数去查询,就需要排除出去
    在这里插入图片描述

    测试3:添加匹配规则的测试
    添加匹配规则:调用 matchingAny() ,相当于 OR 运算符,只要有一个参数和查询的student对象对应得上,就能将该对象查出来
    在这里插入图片描述

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

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

    在这里插入图片描述

    匹配规则方法: .withMatcher() ,还能这样写,更灵活
    .withMatcher(“address”,matcher -> matcher.endsWith())
    也是以该样本属性作为后缀进行查询
    在这里插入图片描述

    完整代码

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    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);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
  • 相关阅读:
    vue+element 树形结构 改成懒加载模式(原理element有),这里只做个人理解笔记
    SSD算法
    C++牛客知识点
    LeetCode117. Populating Next Right Pointers in Each Node II
    HLS最全知识库
    对Transformer的一些理解
    合并后的以太坊会像一个流域
    set和map通过一颗红黑树进行封装
    正则 删除特定字符后面所有的元素(不区分大小写)
    Python三目运算符(三元运算符)用法详解(含Python代码)
  • 原文地址:https://blog.csdn.net/weixin_44411039/article/details/132917970