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. ES6常用的新特性

    1.Let&const <!DOCTYPE html> <html lang="en"> <head> <meta charset ...

  2. 通过fsockopen()方法从中国福彩网获取双色球历史中奖数据

    # 以下代码基于 CI 框架 # public function history_draw($page = 0) { set_time_limit(0); $page++; $up2now = dat ...

  3. 使用StringBuilder构建字符串

    使用StringBuilder构建字符串确实可以提高效率,比“+”要高效不少.但使用时也有一些坑: 首先,我们指定一个StringBuilder,并设置其长度. StringBuilder build ...

  4. 精通Spring Boot

    原 精通Spring Boot—— 第二十一篇:Spring Social OAuth 登录简介 1.什么是OAuth OAuth官网介绍是这样的: An open protocol to allow ...

  5. Java自学-控制流程 switch

    Java的 switch 语句 switch 语句相当于 if else 的另一种表达方式 示例 1 : switch switch可以使用byte,short,int,char,String,enu ...

  6. JMeter学习笔记(十八)——返回的响应数据出现中文乱码_解决方案

    一.问题描述 使用jmeter过程中遇到了请求返回的响应数据出现中文乱码 二.原因分析 当没有对响应数据or响应页面设置支持解析中文的编码时,JMeter则会以默认的ISO-8859-1格式解析,而其 ...

  7. FFmpeg--如何同步音视频的解决方案

    如何同步视频 PTS和DTS 幸运的是,音频和视频流都有一些关于以多快速度和什么时间来播放它们的信息在里面.音频流有采样,视频流有每秒的帧率.然而,如果我们只是简单的通过数帧和乘以帧率的方式来同步视频 ...

  8. Java 单文件、多文件上传 / 实现上传进度条

    博客地址:https://ainyi.com/76 日常,工作 在这里总结一下上传吧(是以前做过的练习,就汇总到个人博客吧) java ssm 框架实现文件上传 实现:单文件上传.多文件上传(单选和多 ...

  9. 【故障解决】enq: PS - contention

    [故障解决]enq: PS - contention 一.1  BLOG文档结构图       一.2  前言部分   一.2.1  导读和注意事项 各位技术爱好者,看完本文后,你可以掌握如下的技能, ...

  10. springboot+vue2.x 解决session跨域失效问题

    服务端SpringBoot2.x   :localhost:8082 前端Vue2.x                 :localhost:81 前后端的端口号不同,为跨域,导致前端访问后端时,每次 ...