zoukankan      html  css  js  c++  java
  • 学习Spring-Data-Jpa(十三)---动态查询接口JpaSpecificationExecutor

    1、JpaSpecificationExecutor

      JPA2引入了一个criteria API,我们可以使用它以编程的形式构建查询。通过编写criteria,动态生成query语句。JpaSpecificationExecutor是Spring-Data-JPA为我们执行基于JPA criteria API的Specification查询接口。想要使用该功能,我们自己的Repository接口继承这个接口就可以了。该接口提供了几个根据Specification进行查询的方法。

      JpaSpecificationExecutor源码:

    import java.util.List;
    import java.util.Optional;
    
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.domain.Sort;
    import org.springframework.data.jpa.domain.Specification;
    import org.springframework.lang.Nullable;
    
    /**
     *  接口,允许执行基于JPA criteria API的Specification查询。
     */
    public interface JpaSpecificationExecutor<T> {
    
        /**
         * 返回匹配给定Specification的单个实体,如果没找到返回 Optional.empty(),如果结果集有多个,抛出IncorrectResultSizeDataAccessException异常
         */
        Optional<T> findOne(@Nullable Specification<T> spec);
    
        /**
         * 返回匹配给定Specification的所有实体
         */
        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);
    }

    2、Specification

          JpaSpecificationExecutor的每一个方法中都有一个Specification参数,Specification接口中的toPredicate方法是该接口的核心方法。Specification可以很容易的在实体上构建一组可扩展的Predicate。然后可以对其进行组合使用,这样JpaRepository就不用为每一种情况都写一个查询方法了。

        toPredicate方法种有三个参数:

           Root<T>,代表查询和操作实体的根,我们可以通过它的get方法来获得我们操作的字段,可以写实体属性字符串,也可以使用@StaticMetamodel标记的类来指定(后面有示例)。 

           CriteriaQuery<?>,抽象了整个查询语句,用来把各个段组合在一起。 

           CriteriaBuilder,用来构建CritiaQuery的构建器对象,其实就相当于条件或者是条件组合,以谓语即Predicate的形式返回。

      Specification源码:

    import static org.springframework.data.jpa.domain.SpecificationComposition.*;
    
    import java.io.Serializable;
    
    import javax.persistence.criteria.CriteriaBuilder;
    import javax.persistence.criteria.CriteriaQuery;
    import javax.persistence.criteria.Predicate;
    import javax.persistence.criteria.Root;
    
    import org.springframework.lang.Nullable;
    
    /**
     * 领域驱动设计意义上的规范。
     */
    public interface Specification<T> extends Serializable {
    
        long serialVersionUID = 1L;
    
        /**
         * 否定给定的Specification
         */
        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));
        }
    
        /**
         * 简单的静态工厂方法,给Specification周围添加一些语法糖
         */
        @Nullable
        static <T> Specification<T> where(@Nullable Specification<T> spec) {
            return spec == null ? (root, query, builder) -> null : spec;
        }
    
        /**
         * 将给定Specification与当前Specification进行and关联
         */
        @Nullable
        default Specification<T> and(@Nullable Specification<T> other) {
            return composed(this, other, (builder, left, rhs) -> builder.and(left, rhs));
        }
    
        /**
         * 将给定specification与当前specification进行or关联
         */
        @Nullable
        default Specification<T> or(@Nullable Specification<T> other) {
            return composed(this, other, (builder, left, rhs) -> builder.or(left, rhs));
        }
    
        /**
         * 以给定根和CriteriaQuery的谓词的形式为引用实体的查询创建WHERE子句。
         */
        @Nullable
        Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
    }

    3、SpecificationComposition

      帮助类,以支持specification的组合模式。

    import java.io.Serializable;
    
    import javax.persistence.criteria.CriteriaBuilder;
    import javax.persistence.criteria.CriteriaQuery;
    import javax.persistence.criteria.Predicate;
    import javax.persistence.criteria.Root;
    
    import org.springframework.data.jpa.domain.Specification;
    import org.springframework.lang.Nullable;
    
    /**
     * 帮助类,以支持specification的组合模式
     */
    class SpecificationComposition {
    
        /**
         * 函数式接口,组合器
         */
        interface Combiner extends Serializable {
            Predicate combine(CriteriaBuilder builder, @Nullable Predicate lhs, @Nullable Predicate rhs);
        }
    
        /**
         *  静态组合方法,将给定的两个Specification用指定的组合器进行组合成新的Specification
         */
        @Nullable
        static <T> Specification<T> composed(@Nullable Specification<T> lhs, @Nullable Specification<T> rhs,
                                             Combiner combiner) {
    
            return (root, query, builder) -> {
    
                Predicate otherPredicate = toPredicate(lhs, root, query, builder);
                Predicate thisPredicate = toPredicate(rhs, root, query, builder);
    
                if (thisPredicate == null) {
                    return otherPredicate;
                }
    
                return otherPredicate == null ? thisPredicate : combiner.combine(builder, thisPredicate, otherPredicate);
            };
        }
    
        /**
         * 将Specification转换为Predicate对象
         */
        private static <T> Predicate toPredicate(Specification<T> specification, Root<T> root, CriteriaQuery<?> query,
                CriteriaBuilder builder) {
            return specification == null ? null : specification.toPredicate(root, query, builder);
        }
    }

    4、使用JPAMetaModelEntityProcessor生成元模型

      4.1、导入hibernate-jpamodelgen依赖

            <!-- 自动生成元模型 -->
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-jpamodelgen</artifactId>
                <version>5.2.17.Final</version>
            </dependency>

      4.2、IDEA配置Annoation Processors  

        

      4.3、和lombok一起使用,添加maven插件(如果两个同时使用,不添加额外配置的话,lombok不生效)

                <!--     JPAMetaModelEntityProcessor与lombok共存       -->
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <compilerArguments>
                            <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor</processor>
                        </compilerArguments>
                    </configuration>
                </plugin>

    5、JpaRepositoryImplementation

       JpaRepository和JpaSpecificationExecutor接口的子类,我们可以直接继承改接口。

    6、使用示例

      6.1、继承接口

       6.2、Specification条件工厂(各条件可以组合使用)

    /**
     * book Specification 条件
     *
     * @author caofanqi
     */
    public class BookSpecs {
    
    
        /**
         *  书名模糊
         */
        public static Specification<Book> bookNameLike(String bookName){
            return (Specification<Book>) (root, query, builder) -> {
                //设置抓取策略,解决N+1条SQL问题
                root.fetch("category", JoinType.LEFT);
                return builder.like(root.get("bookName"),"%" + bookName + "%");
            };
        }
    
    
        /**
         * 大于前六个月的认为是新书
         */
        public static Specification<Book> isNewBook(){
            return (Specification<Book>) (root, query, builder) -> {
                LocalDate beforeSixMonth = LocalDate.now().minusMonths(6);
                root.fetch(Book_.category, JoinType.LEFT);
                return builder.greaterThan(root.get(Book_.publishDate),beforeSixMonth);
            };
        }
    
    
        /**
         *  门类名称模糊
         */
        public static Specification<Book> categoryNameLike(String categoryName){
            return (Specification<Book>) (root, query, builder) -> {
                root.fetch(Book_.category, JoinType.INNER);
                return builder.like(root.get(Book_.category).get(Category_.categoryName),"%" + categoryName + "%");
            };
        }
    
    }

      6.3、单元测试

        @Test
        void testSpec1(){
            List<Book> books = bookRepository.findAll(BookSpecs.bookNameLike("java"));
            books.forEach(b-> System.out.println(b.getBookName()));
        }
    
        @Test
        void testSpec2(){
            List<Book> books = bookRepository.findAll(BookSpecs.bookNameLike("java").and(BookSpecs.isNewBook()));
            books.forEach(b-> System.out.println(b.getBookName()));
        }
    
        /**
         * 排序和分页一样使用
         */
        @Test
        void testSpec3(){
            List<Book> books = bookRepository.findAll(BookSpecs.isNewBook(),Sort.by(Sort.Direction.DESC,"publishDate"));
            books.forEach(b-> System.out.println(b.getBookName()));
        }
    
    
        @Test
        void testSpec4(){
            List<Book> books = bookRepository.findAll(BookSpecs.categoryNameLike("数据库"));
            books.forEach(b-> System.out.println(b.getBookName()));
        }
    源码地址:https://github.com/caofanqi/study-spring-data-jpa
  • 相关阅读:
    搭建docker私有registory (harbor)
    消息队列原理及ActiveMQ、RocketMQ、RabbitMQ、Kafka区别总结
    too many users are authenticated
    docker环境部署nginx、tomcat、redis
    docekr环境部署mysql、kafaka、kafkamanager和mongodb
    在IE6 情况下让PNG图片透明的3种方法
    ecshop商品列表页出现一个空的搜索结果
    ecshop去掉国家省市区,实现手动填写收货地址
    ecshop始终显示全部分类
    ecshop商品列表页增加/显示货号SN
  • 原文地址:https://www.cnblogs.com/caofanqi/p/11973203.html
Copyright © 2011-2022 走看看