学习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
学习Spring-Data-Jpa(十三)---动态查询接口JpaSpecificationExecutor的更多相关文章
- Spring data jpa 复杂动态查询方式总结
一.Spring data jpa 简介 首先我并不推荐使用jpa作为ORM框架,毕竟对于负责查询的时候还是不太灵活,还是建议使用mybatis,自己写sql比较好.但是如果公司用这个就没办法了,可以 ...
- spring data jpa Specification动态查询
package com.ytkj.entity; import javax.persistence.*; import java.io.Serializable; /** * @Entity * 作用 ...
- spring data jpa hql动态查询案例
目的:根据入参条件不同,动态组装hql里的where语句. 1. 实现代码 public List<WrapStatis> queryStatisCriteriaBuilder(Strin ...
- 【Spring Data 系列学习】Spring Data JPA @Query 注解查询
[Spring Data 系列学习]Spring Data JPA @Query 注解查询 前面的章节讲述了 Spring Data Jpa 通过声明式对数据库进行操作,上手速度快简单易操作.但同时 ...
- Spring data JPA 理解(默认查询 自定义查询 分页查询)及no session 三种处理方法
简介:Spring Data JPA 其实就是JDK方式(还有一种cglib的方式需要Class)的动态代理 (需要一个接口 有一大堆接口最上边的是Repository接口来自org.springfr ...
- Spring Data Jpa (三)定义查询方法
本章详细讲解如何利用方法名定义查询方法(Defining Query Methods) (1)定义查询方法的配置方法 由于Spring JPA Repository的实现原理是采用动态代理的机制,所以 ...
- 学习Spring Data JPA
简介 Spring Data 是spring的一个子项目,在官网上是这样解释的: Spring Data 是为数据访问提供一种熟悉且一致的基于Spring的编程模型,同时仍然保留底层数据存储的特殊 ...
- Spring Data JPA应用 之查询分析
在Spring Data JPA应用之常规CRUD操作初体验 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)尾附上了JpaRepository接口继承关系及方法,可以知道JpaRepos ...
- spring data jpa 多对多查询
package com.ytkj.dao; import com.ytkj.entity.Customer; import com.ytkj.entity.Role; import org.sprin ...
随机推荐
- STVD生成hex,bin,显示ram&flash的使用情况
前言: 虽然stvd免费,但使用起来并不令人满意,不能自动补全,界面丑陋,设置繁琐,最难受的是不会自动输出ram和flash的使用情况.当然方法还是有的,下面就讲讲我是怎么实现的.个人水平有限,如有错 ...
- c,使用lib,dll
lib使用: #include "xxx.h" // lib的头文件 #pragma comment(lib, "xxx.lib") 这样会将lib里的数据编译 ...
- Go基础编程实践(五)—— 错误和日志
自定义错误类型 Go中可以使用errors.New()创建错误信息,也可以通过创建自定义错误类型来满足需求.error是一个接口类型,所有实现该接口的类型都可以当作一个错误类型. // error类型 ...
- 6. 运行Spark SQL CLI
Spark SQL CLI可以很方便的在本地运行Hive元数据服务以及从命令行执行任务查询.需要注意的是,Spark SQL CLI不能与Thrift JDBC服务交互.在Spark目录下执行如下命令 ...
- football Alternative form of foteball
football. Alternative form of foteball foteball(Late Middle English) football (game played with a ba ...
- centos 6.5安装zabbix 4.4
一.安装环境 本环境,使用单机部署. 操作系统:centos 7.5 x64zabbix-server,Mysql,php,nginx都在同一台服务器.都是使用Yum安装的! 官方安装文档: http ...
- Spring-AOP切面编程(3)
https://www.jianshu.com/p/be69b874a2a9 目录 1. Web MVC发展史历程2.Spring概要3.Spring-依赖注入概要(IOC)4.属性注入的三种实现方式 ...
- Java 阿拉伯数字转换为中文大写数字
Java 阿拉伯数字转换为中文大写数字 /** * <html> * <body> * <P> Copyright 1994 JsonInternational&l ...
- [golang]图片按中心旋转后,新图的左顶点位置的偏移量
1 前言 图片按中心旋转后,新图的左顶点位置的偏移量 2 代码 func OffsetXYAfterRotationCore(W, H, L, T, Angle float64) (x, y floa ...
- vue自定义组件的递归
1.设置一个div显示数值(这个数值不要设置在组件内部) 2.设置组件的名字 <!DOCTYPE html> <html> <head> <title> ...