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. AtCoder-arc058(题解)

    A - こだわり者いろはちゃん / Iroha's Obsession(暴力) 题目链接 题目大意: 给你 \(k\) 个个位数字和一个数字 \(n\) ,要求找到一个大于等于n的数字,使得不出现 \ ...

  2. 怎样遍历NodeList对象

    因为NodeList对象是一个类似数组的对象, 且它自带了一个 forEach() 方法, 因此可以使用 forEach() 遍历, 它的用法和 Array 里面的 forEach() 是完全一样的. ...

  3. C#/.NET 中启动进程时所使用的 UseShellExecute 设置为 true 和 false 分别代表什么意思?

    原文:C#/.NET 中启动进程时所使用的 UseShellExecute 设置为 true 和 false 分别代表什么意思? 在 .NET 中创建进程时,可以传入 ProcessStartInfo ...

  4. dump net core windbg 内存分析

    !dumpheap -stat //检查当前所有托管类型的统计信息 0:000> !dumpheap -stat //检查当前所有托管类型的统计信息 .... 00007ffdb9387a98 ...

  5. Excel 2010同时打开2个或多个独立窗口

    亲测有效 参考下面的网址 https://jingyan.baidu.com/article/86fae346acca7d3c49121ad4.html 1. 在win+r  输入框里面输入“rege ...

  6. custom drawer

    import 'package:flutter/material.dart'; main() => runApp(MaterialApp( home: HomePage(), )); class ...

  7. MySQL语法顺序及执行顺序

    一.书写顺序 select[distinct] from join on where group by having union order by limit 二.执行顺序 from on join ...

  8. Linux中打开文件显示行号相关命令

    一.显示行号 :set number 或 :set nu 二.取消显示行号 :set nu! 三.每次打开显示行号 修改vi ~/.vimrc 文件,添加:set number

  9. maven 修改镜像为阿里淘宝源

    修改maven根目录下的conf文件夹中的setting.xml文件,(或者当前用户目录 的 .m2 目录下的 setting.xml 文件) 内容如下: <mirrors> <mi ...

  10. Spring @Cacheable注解 && 事务@Transactional 在同一个类中的方法调用不生效

    @Cacheable 注解在对象内部调用不会生效 代码示例:ProductServiceImpl.java public List<ProductInfoVO> getProductLis ...