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的更多相关文章

  1. Spring data jpa 复杂动态查询方式总结

    一.Spring data jpa 简介 首先我并不推荐使用jpa作为ORM框架,毕竟对于负责查询的时候还是不太灵活,还是建议使用mybatis,自己写sql比较好.但是如果公司用这个就没办法了,可以 ...

  2. spring data jpa Specification动态查询

    package com.ytkj.entity; import javax.persistence.*; import java.io.Serializable; /** * @Entity * 作用 ...

  3. spring data jpa hql动态查询案例

    目的:根据入参条件不同,动态组装hql里的where语句. 1. 实现代码 public List<WrapStatis> queryStatisCriteriaBuilder(Strin ...

  4. 【Spring Data 系列学习】Spring Data JPA @Query 注解查询

    [Spring Data 系列学习]Spring Data JPA @Query 注解查询 前面的章节讲述了 Spring Data Jpa 通过声明式对数据库进行操作,上手速度快简单易操作.但同时 ...

  5. Spring data JPA 理解(默认查询 自定义查询 分页查询)及no session 三种处理方法

    简介:Spring Data JPA 其实就是JDK方式(还有一种cglib的方式需要Class)的动态代理 (需要一个接口 有一大堆接口最上边的是Repository接口来自org.springfr ...

  6. Spring Data Jpa (三)定义查询方法

    本章详细讲解如何利用方法名定义查询方法(Defining Query Methods) (1)定义查询方法的配置方法 由于Spring JPA Repository的实现原理是采用动态代理的机制,所以 ...

  7. 学习Spring Data JPA

    简介 Spring Data 是spring的一个子项目,在官网上是这样解释的: Spring Data 是为数据访问提供一种熟悉且一致的基于Spring的编程模型,同时仍然保留底层数据存储的特​​殊 ...

  8. Spring Data JPA应用 之查询分析

    在Spring Data JPA应用之常规CRUD操作初体验 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)尾附上了JpaRepository接口继承关系及方法,可以知道JpaRepos ...

  9. spring data jpa 多对多查询

    package com.ytkj.dao; import com.ytkj.entity.Customer; import com.ytkj.entity.Role; import org.sprin ...

随机推荐

  1. Java基础知识点总结(二)

    “https://www.runoob.com/java” 1.Java运算符优先级 2.内置数据类型的包装类 抽象类Number是所有包装类(Integer.Long.Byte.Double.Flo ...

  2. Python学习之路:函数传递可变参数与不可变参数

    函数传参的方法: 太基础了,8说了 直接上重点 一.可变参数的传递 可变参数有:列表.集合.字典 直接上代码: a = [1, 2] def fun(a): print('传入函数时a的值为:', a ...

  3. c#学习笔记1-简单算法

    using System; namespace Demo { class Studycs { public static void Main(String[] args) { // String re ...

  4. 去除Chrome“请停用以开发者模式运行的扩展程序”提示

    将version.dll放在chrome同级目录,重启浏览器( 79.0.3945.79版本后已失效)                    

  5. 关于 exynos 4412 按键中断 异步通知

    以下是驱动测试代码: //内核的驱动代码 #include <linux/init.h> #include <linux/module.h> //for module_init ...

  6. go上传图片微信服务器<<临时素材

    type WxImage struct { Type string `json:"type"` MediaId string `json:"media_id"` ...

  7. t100 debug常用指令

    1.     r.d 程式代號 Find (Ctrl+F) => Find Next(F3). 設中斷點(Toggle):Double Click => Run/Continue the ...

  8. Python入门 常量 注释 基础数据类型 用户输入 流程控制

    Python入门 一.常量 在Python中,不像其他语言有绝对的常量,修改会报错,在Python中有个约定俗成的规定--常量就是将变量名大写. 尽量保持不更改的一种量 , 这个常量有是干什么的呢 其 ...

  9. 向Spring 容器中注入对象的几种方法

    1.使用@Bean 注解,用于注入第三方 jar 包到SpringIOC容器中. 2.使用 @Import({Order.class, Member.class, MyImportBeanDefini ...

  10. 使用poi调整字体格式、添加单元格注释、自动调整列宽

    1 创建新的工作铺 import java.io.FileOutputStream; import org.apache.poi.hssf.usermodel.HSSFCell; import org ...