• Spring Data JPA 之 JpaSpecificationExecutor 的实现原理


    10 JpaSpecificationExecutor 的实现原理

    通过上⼀课时,我们了解到 JpaSpecificationExecutor 给我们提供了动态查询或者写框架的⼀种思路,那么这节课我们来看⼀下 JpaSpecificationExecutor 的详细⽤法和原理,及其实战应⽤场景中如何实现⾃⼰的框架。

    在开始讲解之前,请先思考⼏个问题:

    1. JpaSpecificationExecutor 如何创建?
    2. 它的使⽤⽅法有哪些?
    3. toPredicate ⽅法如何实现?

    带着这些问题,我们开始探索。先看⼀个例⼦感受⼀下 JpaSpecificationExecutor 具体的⽤法。

    10.1 JpaSpecificationExecutor 的使用案例

    我们假设⼀个后台管理⻚⾯根据 name 模糊查询、sex 精准查询、age 范围查询、时间区间查询、address 的 in 查询这样⼀个场景,来查询 user 信息,我们看看这个例⼦应该怎么写。

    第⼀步:创建 User 和 UserAddress 两个实体。 代码如下:

    /**
     * ⽤户基本信息表
     **/
    @Entity
    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    @ToString(exclude = "addresses")
    public class User implements Serializable {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        private String name;
        private String email;
        @Enumerated(EnumType.STRING)
        private SexEnum sex;
        private Integer age;
        private Instant createDate;
        private Date updateDate;
        @OneToMany(mappedBy = "user")
        @JsonIgnore
        private List<UserAddress> addresses;
    }
    
    enum SexEnum {
     BOY,GIRL
    }
    
    /**
     * ⽤户地址表
     */
    @Entity
    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    @ToString(exclude = "user")
    public class UserAddress {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        private String address;
        @ManyToOne(cascade = CascadeType.ALL)
        private User user;
    }
    
    • 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

    第⼆步:创建 UserRepository 继承 JpaSpecificationExecutor 接⼝。

    public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
    }
    
    • 1
    • 2

    第三步:创建⼀个测试⽤例进⾏测试。

    @DataJpaTest
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    public class UserJpeTest {
        @Autowired
        private UserRepository userRepository;
        @Autowired
        private UserAddressRepository userAddressRepository;
    
        /**
         * 提前创建⼀些数据
         */
        @BeforeAll
        @Rollback(false)
        @Transactional
        void init() {
            User user = User.builder()
                    .name("jack")
                    .email("123456@126.com")
                    .sex(SexEnum.BOY)
                    .age(20)
                    .createDate(Instant.now())
                    .updateDate(new Date())
                    .build();
            userAddressRepository.saveAll(Lists.newArrayList(UserAddress.builder().user(user).address("shanghai").build(),
                    UserAddress.builder().user(user).address("beijing").build()));
        }
    
        @Test
        public void testSPE() {
            // 模拟请求参数
            User userQuery = User.builder()
                    .name("jack")
                    .email("123456@126.com")
                    .sex(SexEnum.BOY)
                    .age(20)
                    .addresses(Lists.newArrayList(UserAddress.builder().address("shanghai").build()))
                    .build();
            // 假设的时间范围参数
            Instant beginCreateDate = Instant.now().plus(-2, ChronoUnit.HOURS);
            Instant endCreateDate = Instant.now().plus(1, ChronoUnit.HOURS);
            // 利⽤ Specification 进⾏查询
            Page<User> users = userRepository.findAll((Specification<User>) (root, query, cb) -> {
                List<Predicate> ps = new ArrayList<>();
                if (StringUtils.isNotBlank(userQuery.getName())) {
                    // 我们模仿⼀下 like 查询,根据 name 模糊查询
                    ps.add(cb.like(root.get("name"), "%" + userQuery.getName() + "%"));
                }
                if (userQuery.getSex() != null) {
                    // equal 查询条件,这⾥需要注意,直接传递的是枚举
                    ps.add(cb.equal(root.get("sex"), userQuery.getSex()));
                }
                if (userQuery.getAge() != null) {
                    // greaterThan ⼤于等于查询条件
                    ps.add(cb.greaterThan(root.get("age"), userQuery.getAge()));
                }
                if (beginCreateDate != null && endCreateDate != null) {
                    // 根据时间区间去查询创建
                    ps.add(cb.between(root.get("createDate"), beginCreateDate, endCreateDate));
                }
                if (!ObjectUtils.isEmpty(userQuery.getAddresses())) {
                    // 联表查询,利⽤ root 的 join ⽅法,根据关联关系表⾥⾯的字段进⾏查询。
                    ps.add(cb.in(root.join("addresses").get("address"))
                            .value(userQuery.getAddresses().stream().map(UserAddress::getAddress).collect(Collectors.toList())));
                }
                return query.where(ps.toArray(new Predicate[0])).getRestriction();
            }, PageRequest.of(0, 2));
            System.out.println(users);
        }
    }
    
    • 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

    我们看⼀下执⾏结果。

    Hibernate: select user0_.id as id1_3_, user0_.age as age2_3_, user0_.create_date as create_d3_3_, user0_.email as email4_3_, user0_.name as name5_3_, user0_.sex as sex6_3_, user0_.update_date as update_d7_3_ from user user0_ inner join user_address addresses1_ on user0_.id=addresses1_.user_id where (user0_.name like ?) and user0_.sex=? and user0_.age>20 and (user0_.create_date between ? and ?) and (addresses1_.address in (?)) limit ?
    
    • 1

    此 SQL 就是查询 User inner Join user_address 之后组合成的查询 SQL,基本符合我们的预期,即不同的查询条件。我们通过这个例⼦⼤概知道了 JpaSpecificationExecutor 的⽤法,那么它具体是什么呢?

    10.2 JpaSpecificationExecutor 的语法详解

    我们依然通过看 JpaSpecificationExecutor 的源码,来了解⼀下它的⼏个使⽤⽅法,如下所示:

    public interface JpaSpecificationExecutor<T> {
        // 根据 Specification 条件查询单个对象,要注意的是,如果条件能查出来多个会报错
        T findOne(@Nullable Specification<T> spec);
        // 根据 Specification 条件,查询 List 结果
        List<T> findAll(@Nullable Specification<T> spec);
        // 根据 Specification 条件,分⻚查询
        Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
        // 根据 Specification 条件,带排序的查询结果
        List<T> findAll(@Nullable Specification<T> spec, Sort sort);
        // 根据 Specification 条件,查询数量
        long count(@Nullable Specification<T> spec); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    其返回结果和 Pageable、Sort,我们在前⾯课时都有介绍过,这⾥我们重点关注⼀下 Specification。看⼀下 Specification 接⼝的代码。

    在这里插入图片描述

    通过看其源码就会发现⾥⾯提供的⽅法很简单。

    // 下⾯是静态⽅法,创建 where 后⾯的 Predicate 集合。
    static <T> Specification<T> where(@Nullable Specification<T> spec) {
        return spec == null ? (root, query, builder) -> null : spec;
    }
    // 这是静态⽅法,创建 Not 的查询条件。
    static <T> Specification<T> not(@Nullable Specification<T> spec) {
        return spec == null
            ? (root, query, builder) -> null
            : (root, query, builder) -> builder.not(spec.toPredicate(root, query, builder));
    }
    // 下⾯⼀段代码表示组合的 and 关系的查询条件
    default Specification<T> and(@Nullable Specification<T> other) {
        return SpecificationComposition.composed(this, other, CriteriaBuilder::and);
    }
    // 下⾯是默认⽅法,创建 or 条件的查询参数。
    default Specification<T> or(@Nullable Specification<T> other) {
        return SpecificationComposition.composed(this, other, CriteriaBuilder::or);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    上⾯这⼏个⽅法⽐较简单,就不⼀⼀细说了,我们主要看⼀下需要实现的⽅法:

    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
    
    • 1

    toPredicate 这个⽅法是我们⽤到的时候需要⾃⼰去实现的,接下来我们详细介绍⼀下。

    ⾸先我们在刚才的 Demo ⾥⾯设置⼀个断点,看到如下界⾯。

    在这里插入图片描述

    这⾥可以分别看到 Root 的实现类是 RootImpl,CriteriaQuery 的实现类是 CriteriaQueryImpl,CriteriaBuilder 的实现类是 CriteriaBuilderImpl。

    • javax.persistence.criteria.Root

    • javax.persistence.criteria.CriteriaQuery

    • javax.persistence.criteria.CriteriaBuilder

    其中,上⾯三个接⼝是 Java Persistence API 定义的接⼝。

    • org.hibernate.query.criteria.internal.path.RootImpl
    • org.hibernate.query.criteria.internal.CriteriaQueryImpl
    • org.hibernate.query.criteria.internal.CriteriaBuilderImpl

    ⽽这个三个实现类都是由 Hibernate 进⾏实现的,也就是说 JpaSpecificationExecutor 封装了原本需要我们直接操作 Hibernate 中 Criteria 的 API ⽅法。

    下⾯分别解释上述三个参数。

    10.2.1 Root root

    代表了可以查询和操作的实体对象的根,如果将实体对象⽐喻成表名,那 root ⾥⾯就是这张表⾥⾯的字段,⽽这些字段只是 JPQL 的实体字段⽽已。我们可以通过⾥⾯的 Path get(String attributeName),来获得我们想要操作的字段。

    类似于我们上⾯的:root.get("createDate") 等操作

    10.2.2 CriteriaQuery query

    代表⼀个 specific 的顶层查询对象,它包含着查询的各个部分,⽐如 select 、from、where、group by、order by 等。CriteriaQuery 对象只对实体类型或嵌⼊式类型的 Criteria 查询起作⽤。简单理解为,它提供了查询 ROOT 的⽅法。常⽤的⽅法有如下⼏种:

    在这里插入图片描述

    正如我们上⾯ where 的⽤法:query.where(.....) ⼀样

    这个语法⽐较简单,我们在其⽅法后⾯加上相应的参数即可。

    例如在我们上的例子上加上 group byquery.where(ps.toArray(new Predicate[0])).groupBy(root.get("age")).getRestriction();

    10.2.3 CriteriaBuilder cb

    CriteriaBuilder 是⽤来构建 CritiaQuery 的构建器对象,其实就相当于条件或者条件组合,并以 Predicate 的形式返回。它基本上提供了所有常⽤的⽅法,如下所示:

    在这里插入图片描述

    我们直接通过此类的 Structure 视图就可以看到都有哪些⽅法。其中,and、any 等⽤来做查询条件的组合;类似 between、equal、exist、ge、gt、isEmpty、isTrue、in 等⽤来做查询条件的查询,类似下图的⼀些⽅法。

    ⽽其中 Expression 很简单,都是通过 root.get(...) 某些字段即可返回,正如下⾯的⽤法。

    Predicate p1 = cb.like(root.get("name").as(String.class), "%" + uqm.getName() + "%");
    Predicate p2 = cb.equal(root.get("uuid").as(Integer.class), uqm.getUuid());
    Predicate p3 = cb.gt(root.get("age").as(Integer.class), uqm.getAge());
    
    • 1
    • 2
    • 3

    我们利⽤ like、equal、gt 可以⽣产 Predicate,⽽ Predicate 可以组合查询。⽐如我们预定它们之间是 and 或 or 的关系: Predicate p = cb.and(p3,cb.or(p1,p2));

    我们让 p1 和 p2 是 or 的关系,并且得到的 Predicate 和 p3 ⼜构成了 and 的关系。你可以发现它的⽤法还是⽐较简单的,正如我们开篇所说的 Junit 中 test ⾥⾯⼀样的写法。

    关于 JpaSpecificationExecutor 的语法我们就介绍完了,其实它⾥⾯的功能相当强⼤,只是我们发现 Spring Data JPA 介绍得并不详细,只是⼀笔带过,可能他们认为写框架的⼈才能⽤到,所以介绍得不多。

    如果你想了解更多语法的话,可以参考 Hibernate 的⽂档:https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#criteria。我们再来看看 JpaSpecificationExecutor 的实现原理。

    10.3 JpaSpecificationExecutor 原理分析

    我们先看⼀下 JpaSpecificationExecutor 的类图。

    在这里插入图片描述

    从图中我们可以看得出来:

    1. JpaSpecificationExecutor 和 JpaRepository 是平级接⼝,⽽它们对应的实现类都是 SimpleJpaRepository;
    2. Specification 被 ExampleSpecification 和 JpaSpecificationExector 使⽤,⽤来创建查询;
    3. Predicate 是 JPA 协议⾥⾯提供的查询条件的根基;
    4. SimpleJpaRepository 利⽤ EntityManager 和 criteria 来实现由 JpaSpecificationExector 组合的 query。

    那么我们再直观地看⼀下 JpaSpecificationExecutor 接⼝⾥⾯的⽅法 findAll 对应的 SimpleJpaRepository ⾥⾯的实现⽅法 findAll,我们通过⼯具可以很容易地看到相应的实现⽅法,如下所示:

    在这里插入图片描述

    得到 TypeQuery 就可以直接操作 JPA 协议⾥⾯相应的⽅法了,那么我们看下 getQuery(spec,pageable) 的实现过程。

    在这里插入图片描述

    之后⼀步⼀步 debug 就可以了。

    在这里插入图片描述

    然后再看看 applySpecificationToCriteria 方法

    在这里插入图片描述

    到了上图所示这⾥,就可以看到:

    1. Specification spec 是我们测试⽤例写的 specification 的匿名实现类;
    2. 由于是⽅法传递,所以到图示断点的时候,才会执⾏我们在测试⽤⾥⾯写的 Specification;
    3. 我们可以看到这个⽅法最后是调⽤的 EntityManager,⽽ EntitytManger 是 JPA 操作实体的核⼼原理,我在下⼀课时讲⾃定义 Repsitory 的时候再详细介绍;
    4. 从上⾯的⽅法实现过程中我们可以看得出来,所谓的 JpaSpecificationExecutor 原理,⽤⼀句话概况,就是利⽤ Java Persistence API 定义的接⼝和 Hibernate 的实现,做了⼀个简单的封装,⽅便我们操作 JPA 协议中 criteria 的相关⽅法。

    到这⾥,原理和使⽤⽅法,我们基本介绍完了。你可能会有疑问:这个感觉有点重要,但是⼀般⽤不到吧?那么接下来我们看看 JpaSpecificationExecutor 的实战应⽤场景是什么样的。

    10.4 JpaSpecificationExecutor 实战

    其实JpaSpecificationExecutor 的⽬的不是让我们做⽇常的业务查询,⽽是给我们提供了⼀种⾃定义 Query for rest 的架构思路,如果做⽇常的增删改查,肯定不如我们前⾯介绍的 Defining Query Methods 和 @Query ⽅便。

    那么来看下,实战过程中如何利⽤ JpaSpecificationExecutor 写⼀个框架。

    10.4.1 ⾃定义 MySpecification

    我们可以⾃定义⼀个 Specification 的实现类,它可以实现任何实体的动态查询和各种条件的组合。

    public class MySpecification<Entity> implements Specification<Entity> {
        private final SearchCriteria criteria;
    
        public MySpecification(SearchCriteria criteria) {
            this.criteria = criteria;
        }
    
        /**
         * 实现实体根据不同的字段、不同的Operator组合成不同的Predicate条件
         *
         * @param root    must not be {@literal null}.
         * @param query   must not be {@literal null}.
         * @param builder must not be {@literal null}.
         * @return a {@link Predicate}, may be {@literal null}.
         */
        @Override
        public Predicate toPredicate(Root<Entity> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
            switch (criteria.getOperation()) {
                case GT:
                    return builder.greaterThanOrEqualTo(root.get(criteria.getKey()), criteria.getValue().toString());
                case LT:
                    return builder.lessThanOrEqualTo(root.get(criteria.getKey()), criteria.getValue().toString());
                case LK:
                    if (root.get(criteria.getKey()).getJavaType() == String.class) {
                        return builder.like(root.get(criteria.getKey()), "%" + criteria.getValue() + "%");
                    } else {
                        return builder.equal(root.get(criteria.getKey()), criteria.getValue());
                    }
                default:
                    return null;
            }
        }
    }
    
    • 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

    我们通过 泛型,解决不同实体的动态查询(当然了,我只是举个例⼦,这个⽅法可以进⾏⽆限扩展)。我们通过 SearchCriteria 可以知道不同的字段是什么、值是什么、如何操作的,看⼀下代码:

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public class SearchCriteria {
        private String key;
        private Operator operation;
        private Object value;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    其中的 Operator 也是我们⾃定义的。

    @Getter
    @RequiredArgsConstructor
    public enum Operator {
        /**
         * 等于
         */
        EQ("="),
        /**
         * 等于
         */
        LK(":"),
        /**
         * 不等于
         */
        NE("!="),
        /**
         * ⼤于
         */
        GT(">"),
        /**
         * ⼩于
         */
        LT("<"),
        /**
         * ⼤于等于
         */
        GE(">=");
    
        private final String operator;
    
        public static Operator fromOperator(String operator) {
            for (Operator v: Operator.values()) {
                if (v.operator.equals(operator)) {
                    return v;
                }
            }
            return EQ;
        }
    }
    
    • 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

    在 Operator 枚举⾥⾯定义了逻辑操作符(⼤于、⼩于、不等于、等于、⼤于等于……也可以⾃⼰扩展),并在 MySpecification ⾥⾯进⾏实现。那么我们来看看它是怎么⽤的,写⼀个测试⽤例试⼀下。

    /**
     * 测试⾃定义的 Specification 语法
     */
    @Test
    public void givenLast_whenGettingListOfUsers_thenCorrect() {
        MySpecification<User> name = 
            new MySpecification<User>(new SearchCriteria("name", Operator.LK,"jack"));
        MySpecification<User> age =
            new MySpecification<User>(new SearchCriteria("age", Operator.GT, 2));
        List<User> results = userRepository.findAll(Specification.where(name).and(age));
        System.out.println(results.get(0).getName());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    你就会发现,我们在调⽤ findAll 组合 Predicate 的时候就会⾮常简单,省去了各种条件的判断和组合,⽽省去的这些逻辑可以全部在我们的框架代码 MySpecification ⾥⾯实现。

    那么如果把这个扩展到 API 接⼝层⾯会是什么样的结果呢?我们来看下。

    10.5 利⽤ Specification 创建 search 为查询条件的 Rest API 接口

    先创建⼀个 Controller,⽤来接收 search 这样的查询条件:类似 userssearch=lastName:doe,age>25 的参数。

    @RestController
    public class UserController {
        
        @Autowired
        private UserRepository repo;
        
        @RequestMapping(method = RequestMethod.GET, value = "/users")
        @ResponseBody
        public List<User> search(@RequestParam(value = "search") String search) {
            Specification<User> spec = new SpecificationsBuilder<User>().buildSpecification(search);
            return repo.findAll(spec);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Controller ⾥⾯⾮常简单,利⽤ SpecificationsBuilder ⽣成我们需要的 Specification 即可。

    那么我们看看 SpecificationsBuilder ⾥⾯是怎么写的。

    public class SpecificationsBuilder<Entity> {
    
        private final List<SearchCriteria> params;
    
        // 初始化 params,保证每次实例都是一个新的 ArrayList
        public SpecificationsBuilder() {
            params = new ArrayList<>();
        }
    
        // 利用正则表达式取我们search参数里面的值,解析成SearchCriteria对象
        public Specification<Entity> buildSpecification(String search) {
            Pattern pattern = Pattern.compile("(\\w+?)([:<>])(\\w+?),");
            Matcher matcher = pattern.matcher(search + ",");
            while (matcher.find()) {
                this.with(matcher.group(1), Operator.fromOperator(matcher.group(2)), matcher.group(3));
            }
            return this.build();
        }
    
        // 根据参数返回我们刚才创建的 SearchCriteria
        private SpecificationsBuilder<Entity> with(String key, Operator operation, Object value) {
            params.add(new SearchCriteria(key, operation, value));
            return this;
        }
    
        // 根据我们刚才创建的 MySpecification 返回所需要的 Specification
        private Specification<Entity> build() {
            if (params.size() == 0) {
                return null;
            }
    
            List<Specification<Entity>> specs = params.stream()
                    .map(MySpecification<Entity>::new)
                    .collect(Collectors.toList());
    
            Specification<Entity> result = specs.get(0);
    
            for (int i = 1; i < params.size(); i++) {
                result = Specification.where(result).and(specs.get(i));
            }
            return result;
        }
    }
    
    • 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

    通过上⾯的代码,我们可以看到通过⾃定义的 SpecificationsBuilder,来处理请求参数 search ⾥⾯的值,然后转化成我们上⾯写的 SearchCriteria 对象,再调⽤ MySpecification ⽣成我们需要的 Specification,从⽽利⽤ JpaSpecificationExecutor 实现查询效果。是不是学起来并不困难了,你学会了吗?

    10.6 总结

    本课时,我们通过实例学习了 JpaSpecificationExecutor 的⽤法,并且通过源码了解了 JpaSpecificationExecutor 的实现原理,最后我举了⼀个实战场景的例⼦,使我们可以利⽤ Spring Data JPA and Specifications 很轻松地创建⼀个基于 Search 的 Rest API。虽然我介绍的这个例⼦还有很多可以扩展的地⽅,但是希望你根据实际情况再进⾏相应的扩展。

  • 相关阅读:
    电子眼与无人机在城市安防中的协同应用研究
    图解网络(三)——TCP篇01
    CAD二次开发--CAD2007(.Net3.5环境)等低版本CAD二次开发在VS中无法捕获断点调试解决办法(CAD二次开发无法断点调试解决办法)
    人工智能第2版学习——人工智能中的逻辑1
    机器学习中的各种损失函数(L1,L2,smoothL1,交叉熵 )
    Jmeter接口自动化(三)逻辑控制器
    人工智能培训老师叶梓:如何通过Prompt优化提升GPT-4性能
    20221106
    4 JavaScript
    STM32CubeMX 学习(6)外部中断实验
  • 原文地址:https://blog.csdn.net/qq_40161813/article/details/125961468