结合SpringDataJPA中的PagingAndSortingRepository和 JpaSpecificationExecutor接口实现大数据量的数据分页查询

其中PagingAndSortingRepository用于分页和排序功能的实现,JpaSpecificationExecutor用于条件查询(主要是针对一张表中的数据进行查询)


一、下面先针对这两个接口进行详细的介绍,第二部分为实际应用的实例,方便实践和测试:
第一部分:PagingAndSortingRepository
1.PagingAndSortingRepository接口提供了一下接口:
Iterable<T> findAll(Sort var1);   //得到经过排序的所有数据
Page<T> findAll(Pageable var1);   //经过分页之后的某一页的数据

其中第二个findAll(Pageable var1)中封装的功能特别强大,参数是Pageable类型,这个var1参数中包含了pageNumber和PageSize两个参数,当然,还可以添加Sort参数,在分页时进行排序。像这样:

Pageable page = PageRequest(pageNumber,pageSize,sort)
Pageable page = PageRequest(pageNumber,pageSize)

这个page对象中存储了所有分页的信息.

2.Pageable

Pageable 是一个接口,他的实现类是PageRequest。这个PageRequest有三个实现构造方法:
(下面的代码是从网上复制粘贴的,仅用于展示其操作)

//这个构造出来的分页对象不具备排序功能
public PageRequest(int page, int size) {
    this(page, size, (Sort)null);
}
//Direction和properties用来做排序操作
public PageRequest(int page, int size, Direction direction, String... properties) {
    this(page, size, new Sort(direction, properties));
}
//自定义一个排序的操作
public PageRequest(int page, int size, Sort sort) {
    super(page, size);
    this.sort = sort;
}

这里外加一个衍生查询的定义,需要的时候可以用到:
PagingAndSortingRepository接口中只有findAll方法,想要实现分页我就就要集成这个接口,当然我们在使用PagingAndSortingRepository提供的findAll方法的同时,还可以使用SpringDataJPA中Repository的衍生查询,(也就是自定义根据属性名称或者是添加@Query查询的方法,比如:findByAge) ,在这些衍生查询方法的参数中只要把pageable参数放置在最后一个,就可以实现同时分页的功能。像是这样(下面是一段手写的kotlin代码):

fun findByAge:Iterable<T>(pageNumber:int,pageSize:int,age:Int){
    Val page = PageRequest(pageNumber,pageSize)
    Val list = findByAge(age,page)
    Return list
}
第二部分:

SpringDataJPA在提供分页的同时,也提供了类似于hibernate的Criteria的查询方式:

要使用这种方式,就需要继承我上面说过的接口:JpaSpecificationExecutor,这个接口有以下方法:

    Optional<T> findOne(@Nullable Specification<T> var1);
    List<T> findAll(@Nullable Specification<T> var1);
    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
    List<T> findAll(@Nullable Specification<T> var1, Sort var2);
    long count(@Nullable Specification<T> var1);

这个接口通过Specification来定义查询的条件,(也就是说,在这里,在使用Specification查询条件的同时,使用上面第三个方法,将Pageable作为参数传入,就能实现查询分页。)在实现Specification这个接口的同时,重写其中的toPredict方法,自定义查询的条件。用kotlin语言实现的方法如下:

    class mySpec : Specification<User> {
          override fun toPredicate(root: Root<User>, criteriaQuery: CriteriaQuery<*>, criteriaBuilder: CriteriaBuilder): Predicate {
          val userName: Path<String> = root["name"]
          return criteriaBuilder.equal(userName, name)
          }
    }

上面是一个简单的equal的JPQL语句的构建,当然这里可以构建更多的查询方式,是这样来链接的,形成or或者and语句.

这里的 root[“name”]中的root是当前查询的表名称,name为root实体中的一个属性名。
CriteriaBuilder接口中提供了很多方法:
Equal:val predict:Predicate = criteriaBuilder.equal(userName, name)
Like: val predict:Predicate = criteriaBuilder.like(userName, “%${name}%”)
lessThan:val predict:Predicate = criteriaBuilder.lessThan(createDate, createAt)
等等。其中第一个参数为Path类型,第二个参数是查询条件,如果这个参数值符合查询条件,则会被筛选出来。


二、实践代码实例

这里关于这两个接口的详细介绍就完了,下面是我实际中写的代码实例:为了不暴露代码还要体现出使用的基本逻辑,暂时改为用User类来代替。

首先:Dao层的接口继承
interface PagingRepository : PagingAndSortingRepository<User, Int>, JpaSpecificationExecutor<User>
service层和controller层

然后,我这里是没有写serviceImpl的,因为这边代码量少,也不属于单独一个模块,所以直接将实现方法写在了controller层中。(哈哈,这里说代码量少…确实是少,因为我们初期如果不适用、SpringDataJPA中的接口,想要实现分页模糊查询,可能要写很多复杂的sql语句以及代码的实现,模糊查询写一些sql语句去实现,分页新建一个Page类,在每次请求使用这个工具类,并且可能还需要表与表之间的级联查询。好在我是在同一张表中查数据。 那么问题来了,怎么样实现大数据量中的符合条件的分页查询功能呢?这个就会很难链接到一起同时实现,我目前能想到的就是先吧所有的都查出来,再分页发送给前端,这样做只是减少了前端的鸭梨,后台还是同样的工作。 但是使用了这两个接口就不一样啦,短短几行代码就搞定。)
**—————————————————这里是分割线
下面实现 根据user 的name、age、createDate进行筛选查询分页功能的实现,其中,name进行模糊查询,age查询符合某个年龄段的User进行相等查询,createDate为符合某个用户创建时间段的查询。这三种要求进行and操作。

fun loadFilesByGroupTag(@RequestParam pageNumber: Int, @RequestParam pageSize: Int,@PathVariable userName: String, @RequestParam userAge: Int,@RequestParam createDate: Int): Page<Any> {

        class mySpec : Specification<File> {
            override fun toPredicate(root: Root<File>, criteriaQuery: CriteriaQuery<*>, criteriaBuilder: CriteriaBuilder): Predicate {
          val userName: Path<String> = root["name"]
          val userAge: Path<Int> = root["Age"]
          val userCreateDate: Path<Date> = root["createDate"]

          //这里可以将上面的uName的定义直接写在下面的like语句中,但是这里equal语句是不能写的,会报错,除非单独写出来,通过Path<T>来定义他的类型。可以多加尝试。
          val p1 = criteriaBuilder.like(userName,"%uName%") //uName为用户模糊筛选的值
          val p2 = criteriaBuilder.equal(userAge,uAge)
          val p3 = criteriaBuilder.lessThan(usercreateDate,"%createDate%") //小于...

          return criteriaBuilder.and(p1,p2,p3) //如果是或语句,这里用or就ok
            }
        }

        val pageable = PageRequest(pageNumber, pageSize)
        val page = pagingRepository.findAll(mySpec(), pageable)
        println("当前第几页:" + (page.number + 1))
        println("当前页总共有几条数据:" + page.numberOfElements)
        println("总共多少条符合条件的数据:" + page.totalElements)
        println("总共多少页:" + page.totalPages)

        return page
}

看明白了没?
不过针对上面这种多条件的查询,常涉及到的功能是多条件模糊查询的集合。而有些条件用户可能不想进行筛选,这时候,就涉及到上面的条件p1/p2/p3中可能有一个为空,或者两个为空。然后criteriaBuilder.and()或者criteriaBuilder.or()中的参数是不能为空的,这就遇到一个比较头疼的问题,用kotlin来解决的话,用机械式的笨拙方法解决,写出来的代码实在是太丑了,也就是吧这些条件添加到一个集合中,去空之后,再进行forEach到criteriaBuilder中。下面记录一下比较好的解决方法:

fun loadFilesByGroupTag(@RequestParam pageNumber: Int, @RequestParam pageSize: Int,@PathVariable userName: String, @RequestParam userAge: Int,@RequestParam createDate: Int): Page<Any> {

       class mySpec : Specification<File> {
            override fun toPredicate(root: Root<File>, criteriaQuery: CriteriaQuery<*>, criteriaBuilder: CriteriaBuilder): Predicate {
          val userName: Path<String> = root["name"]
          val userAge: Path<Int> = root["Age"]
          val userCreateDate: Path<Date> = root["createDate"]

          val p1 = criteriaBuilder.like(userName,"%uName%")
          val p2 = criteriaBuilder.equal(userAge,uAge)
          val p3 = criteriaBuilder.lessThan(usercreateDate,"%createDate%")

          val list = mutableListOf<Predicate>()
                with(criteriaBuilder) {
                    uName?.let { list.add(like(userName, "%$uName%")) }
                    uAge?.let { list.add(like(userAge, "%$uAge")) }
                    createDate?.let { list.add(equal(userCreateDate, createDate)) }
                }
                return and(*list.toTypedArray()) //这里将list先装换为array,然后再展开,涉及到list的varags
           }
        }

        val pageable = PageRequest(pageNumber, pageSize)
        val page = pagingRepository.findAll(mySpec(), pageable)

        return page
    }

拓展:关于list的varags的操作

//Use spread operator, see example from M2 release notes472. Unfortunately, it is not described in main Koltin docs. 
        fun printAll(vararg a : String) {
          for (item in a) println(item)
        }

        fun main(args: Array<String>) {
          printAll("one", "two")
          printAll(*args)
        }
三、运算符集合
运算符:
 /**
     * 运算符
     */
    public enum Operator {
        /** 等于 */
        eq(" = "),
        /** 不等于 */
        ne(" != "),
        /** 大于 */
        gt(" > "),
        /** 小于 */
        lt(" < "),
        /** 大于等于 */
        ge(" >= "),
        /** 小于等于 */
        le(" <= "),
        /** 类似 */
        like(" like "),
        /** 包含 */
        in(" in "),
        /** 为Null */
        isNull(" is NULL "),
        /** 不为Null */
        isNotNull(" is not NULL ");
        Operator(String operator) {
            this.operator = operator;
        }
        private String operator;
        public String getOperator() {
            return operator;
        }
        public void setOperator(String operator) {
            this.operator = operator;
        }
    }

参考:kotlin中文官网
spring data jpa 利用JpaSpecificationExecutor做复杂查询
Spring Boot系列(五):spring data jpa的使用
SpringBoot第二讲利用Spring Data JPA实现数据库的访问(二)_分页和JpaSpecificationExecutor接口介绍

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值