学习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 ...
随机推荐
- Java基础知识点总结(二)
“https://www.runoob.com/java” 1.Java运算符优先级 2.内置数据类型的包装类 抽象类Number是所有包装类(Integer.Long.Byte.Double.Flo ...
- Python学习之路:函数传递可变参数与不可变参数
函数传参的方法: 太基础了,8说了 直接上重点 一.可变参数的传递 可变参数有:列表.集合.字典 直接上代码: a = [1, 2] def fun(a): print('传入函数时a的值为:', a ...
- c#学习笔记1-简单算法
using System; namespace Demo { class Studycs { public static void Main(String[] args) { // String re ...
- 去除Chrome“请停用以开发者模式运行的扩展程序”提示
将version.dll放在chrome同级目录,重启浏览器( 79.0.3945.79版本后已失效)
- 关于 exynos 4412 按键中断 异步通知
以下是驱动测试代码: //内核的驱动代码 #include <linux/init.h> #include <linux/module.h> //for module_init ...
- go上传图片微信服务器<<临时素材
type WxImage struct { Type string `json:"type"` MediaId string `json:"media_id"` ...
- t100 debug常用指令
1. r.d 程式代號 Find (Ctrl+F) => Find Next(F3). 設中斷點(Toggle):Double Click => Run/Continue the ...
- Python入门 常量 注释 基础数据类型 用户输入 流程控制
Python入门 一.常量 在Python中,不像其他语言有绝对的常量,修改会报错,在Python中有个约定俗成的规定--常量就是将变量名大写. 尽量保持不更改的一种量 , 这个常量有是干什么的呢 其 ...
- 向Spring 容器中注入对象的几种方法
1.使用@Bean 注解,用于注入第三方 jar 包到SpringIOC容器中. 2.使用 @Import({Order.class, Member.class, MyImportBeanDefini ...
- 使用poi调整字体格式、添加单元格注释、自动调整列宽
1 创建新的工作铺 import java.io.FileOutputStream; import org.apache.poi.hssf.usermodel.HSSFCell; import org ...