其中PagingAndSortingRepository用于分页和排序功能的实现,JpaSpecificationExecutor用于条件查询(主要是针对一张表中的数据进行查询)
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对象中存储了所有分页的信息.
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类来代替。
interface PagingRepository : PagingAndSortingRepository < User, Int > , JpaSpecificationExecutor < User >
然后,我这里是没有写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接口介绍
凌云 2018-10-23
凌云 2018-12-18
恒美 2016-05-14
凌云 2018-10-16
凌云 2018-10-16
吟风 2016-09-20
凌云 2019-01-03
吟风 2018-05-31
凌云 2018-10-16
穆德 2023-04-06