电脑报价网站,珠宝首饰网站源码,上海做家纺的公司网站,淘宝美工需要学什么软件QueryByExampleExecutor用法
QueryByExampleExecutor#xff08;QBE#xff09;是一种用户友好的查询技术#xff0c;具有简单的接口#xff0c;它允许动态查询创建#xff0c;并且不需要编写包含字段名称的查询。
下面是一个 UML 图#xff0c;你可以看到 QueryByExam…QueryByExampleExecutor用法
QueryByExampleExecutorQBE是一种用户友好的查询技术具有简单的接口它允许动态查询创建并且不需要编写包含字段名称的查询。
下面是一个 UML 图你可以看到 QueryByExampleExecutor 是 JpaRepository 的父接口也就是 JpaRespository 里面继承了 QueryByExampleExecutor 的所有方法。 图一Repository 类图
QBE 的基本语法
QBE 的基本语法可以分为下述几种。
复制代码
public interface QueryByExampleExecutorT { //根据“实体”查询条件查找一个对象
S extends T S findOne(ExampleS example);
//根据“实体”查询条件查找一批对象
S extends T IterableS findAll(ExampleS example);
//根据“实体”查询条件查找一批对象可以指定排序参数
S extends T IterableS findAll(ExampleS example, Sort sort);//根据“实体”查询条件查找一批对象可以指定排序和分页参数
S extends T PageS findAll(ExampleS example, Pageable pageable);
//根据“实体”查询条件查找返回符合条件的对象个数
S extends T long count(ExampleS example);
//根据“实体”查询条件判断是否有符合条件的对象
S extends T boolean exists(ExampleS example);
}你可以看到这几个语法其实差不多下面我们用 Page findAll 写一个分页查询的例子看一下效果。
QueryByExampleExecutor 的使用案例
我们还用先前的 User 实体和 UserAddress 实体并把 User 变丰富一点这样方便测试。两个实体关键代码如下。
复制代码
Entity
Data
Builder
AllArgsConstructor
NoArgsConstructor
ToString(exclude address)
public class User implements Serializable {IdGeneratedValue(strategy GenerationType.AUTO)private Long id;private String name;private String email;Enumerated(EnumType.STRING)private SexEnum sex;private Integer age;private Instant createDate;private Date updateDate;OneToMany(mappedBy user,fetch FetchType.EAGER,cascade {CascadeType.ALL})private ListUserAddress address;
}
enum SexEnum {BOY,GIRL
}
//User实体我们扩充了一些字段去了不同的类型方便测试
Entity
Data
Builder
AllArgsConstructor
NoArgsConstructor
ToString(exclude user)
public class UserAddress {IdGeneratedValue(strategy GenerationType.AUTO)private Long id;private String address;ManyToOne(cascade CascadeType.ALL)JsonIgnoreprivate User user;
}
//UserAddress基本上不变可以看出两个实体我们加了些字段。UserAddressRepository 继承 JpaRepository从而也继承了 QueryByExampleExceutor 里面的方法如下所示。
复制代码
public interface UserAddressRepository extends JpaRepositoryUserAddress,Long {
}那么我们写一个测试用例来熟悉一下 QBE 的语法看一下完整的测试用例的写法。
复制代码
package com.example.jpa.example1;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.annotation.Rollback;
import javax.transaction.Transactional;
import java.time.Instant;
import java.util.Date;
DataJpaTest
TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class UserAddressRepositoryTest {Autowiredprivate UserAddressRepository userAddressRepository;private Date now new Date();/*** 负责添加数据假设数据库里面已经有的数据*/BeforeAllRollback(false)Transactionalvoid init() {User user User.builder().name(jack).email(123456126.com).sex(SexEnum.BOY).age(20).createDate(Instant.now()).updateDate(now).build();userAddressRepository.saveAll(Lists.newArrayList(UserAddress.builder().user(user).address(shanghai).build(),UserAddress.builder().user(user).address(beijing).build()));}TestRollback(false)public void testQBEFromUserAddress() throws JsonProcessingException {User request User.builder().name(jack).age(20).email(12345).build();UserAddress address UserAddress.builder().address(shang).user(request).build();ObjectMapper objectMapper new ObjectMapper();
// System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(address)); //可以打印出来看看参数是什么
//创建匹配器即如何使用查询条件ExampleMatcher exampleMatcher ExampleMatcher.matching().withMatcher(user.email, ExampleMatcher.GenericPropertyMatchers.startsWith()).withMatcher(address, ExampleMatcher.GenericPropertyMatchers.startsWith());PageUserAddress u userAddressRepository.findAll(Example.of(address,exampleMatcher), PageRequest.of(0,2));System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(u));}
}其中方法 testQBEFromUserAddress 负责测试 QBE那么假设我们要写 API 的话前端给我们的查询参数如下。
复制代码
{id : null,address : shang,user : {id : null,name : jack,email : 12345,sex : null,age : 20,createDate : null,updateDate : null}
}想要满足 email 前缀匹配、地址前缀匹配的动态查询条件我们可以跑一下测试用例看一下结果。
复制代码
Hibernate: select useraddres0_.id as id1_2_, useraddres0_.address as address2_2_, useraddres0_.user_id as user_id3_2_ from user_address useraddres0_ inner join user user1_ on useraddres0_.user_iduser1_.id where user1_.age20 and (user1_.email like ? escape ?) and user1_.name? and (useraddres0_.address like ? escape ?) limit ?
2020-09-20 23:04:24.391 TRACE 62179 --- [ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [12345%]
2020-09-20 23:04:24.391 TRACE 62179 --- [ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [CHAR] - [\]
2020-09-20 23:04:24.392 TRACE 62179 --- [ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [jack]
2020-09-20 23:04:24.392 TRACE 62179 --- [ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [VARCHAR] - [shang%]
2020-09-20 23:04:24.393 TRACE 62179 --- [ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [5] as [CHAR] - [\]其中我们可以看到传进来的参数和最终执行的 SQL还挺符合我们的预期的所以我们也能得到正确响应的查询结果如下图 也就是一个地址带一个 User 结果。
那么接下来我们分析一下 Example 这个参数看看它具体的语法是什么。
Example 语法详解
关于 Example 的语法我们直接看一下它的源码吧比较简单。
复制代码
public interface ExampleT {static T ExampleT of(T probe) {return new TypedExample(probe, ExampleMatcher.matching());}static T ExampleT of(T probe, ExampleMatcher matcher) {return new TypedExample(probe, matcher);}//实体参数T getProbe();//匹配器ExampleMatcher getMatcher();//回顾一下我们上一课时讲解的类型这个是返回实体参数的Class TypeSuppressWarnings(unchecked)default ClassT getProbeType() {return (ClassT) ProxyUtils.getUserClass(getProbe().getClass());}
}而 TypedExample 这个类不是 public 的看如下源码。
复制代码
ToString
EqualsAndHashCode
RequiredArgsConstructor(access AccessLevel.PACKAGE)
Getter
class TypedExampleT implements ExampleT {private final NonNull T probe;private final NonNull ExampleMatcher matcher;
}其中我们发现三个类Probe、ExampleMatcher 和 Example分别做如下解释
Probe这是具有填充字段的域对象的实际实体类即查询条件的封装类又可以理解为查询条件参数必填。ExampleMatcherExampleMatcher 有关如何匹配特定字段的匹配规则它可以重复使用在多个实例中必填。ExampleExample 由 Probe 探针和 ExampleMatcher 组成它用于创建查询即组合查询参数和参数的匹配规则。
通过 Example 的源码我们发现想创建 Example 的话只有两个方法
static Example of(T probe)需要一个实体参数即查询的条件。而里面的 ExampleMatcher 采用默认的 ExampleMatcher.matching() 表示忽略 Null所有字段采用精准匹配。static Example of(T probe, ExampleMatcher matcher)需要两个参数构建 Example也就表示了 ExampleMatcher 自由组合规则正如我们上面的测试用例里面的代码一样。
那么现在又遇到个类ExampleMatcher我们分析一下它的语法。
ExampleMatcher 语法分析
我们通过分析 ExampleMatcher 的源码来分析一下其用法。
首先打开 Structure 视图看看里面对外暴露的方法都有哪些。 通过 Structure 视图可以很容易地发现我们要关心的方法都是这些 public 类型的返回 ExampleMatcher 的方法那么我们把这些方法搞明白了是不是就可以掌握其详细用法了呢下面看看它的实现类。 TypedExampleMatcher 不是 public 类型的所以我们可以基本上不用看了主要看一下接口里面给我们暴露了哪些实例化方法。
初始化 ExampleMatcher 实例的方法
查看初始化 ExampleMatcher 实例的方法时我们发现只有如下三个。
先看一下前两个方法
复制代码
//默认matching方法
static ExampleMatcher matching() {return matchingAll();
}
//matchingAll默认的方法
static ExampleMatcher matchingAll() {return new TypedExampleMatcher().withMode(MatchMode.ALL);
}我们看到上面的两个方法所表达的意思是一样的只不过一个是默认一个是方法名上面有语义的。两者采用的都是 MatchMode.ALL 的模式即 AND 模式生成的 SQL 为如下形式
复制代码
Hibernate: select useraddres0_.id as id1_2_, useraddres0_.address as address2_2_, useraddres0_.user_id as user_id3_2_ from user_address useraddres0_ inner join user user1_ on useraddres0_.user_iduser1_.id where user1_.age20 and user1_.name? and (user1_.email like ? escape ?) and (useraddres0_.address like ? escape ?) limit ?可以看到这些查询条件之间都是 AND 的关系。
我们再看一下方法三
复制代码
static ExampleMatcher matchingAny() {return new TypedExampleMatcher().withMode(MatchMode.ANY);
}第三个方法和前面两个方法的区别在于第三个 MatchMode.ANY表示查询条件是 or 的关系我们看一下 SQL
复制代码
Hibernate: select count(useraddres0_.id) as col_0_0_ from user_address useraddres0_ inner join user user1_ on useraddres0_.user_iduser1_.id where useraddres0_.address like ? escape ? or user1_.age20 or user1_.email like ? escape ? or user1_.name?以上就是三个初始化 ExampleMatcher 实例的方法你在运用中需要注意 and 和 or 的关系。 那么我们再看一下 ExampleMatcher 语法给我们暴露的方法有哪些。
ExampleMatcher 语法给我们暴露的方法
忽略大小写
关于忽略大小写我们看下代码
复制代码
//默认忽略大小写的方式默认 False。
ExampleMatcher withIgnoreCase(boolean defaultIgnoreCase);
//提供了一个默认的实现方法忽略大小写
default ExampleMatcher withIgnoreCase() {return withIgnoreCase(true);
}
//哪些属性的paths忽略大小写可以指定多个参数
ExampleMatcher withIgnoreCase(String... propertyPaths);NULL 值的 property 怎么处理
暴露的 Null 值处理方式如下
复制代码
ExampleMatcher withNullHandler(NullHandler nullHandler);我们直接看参数 NullHandler枚举值即可有两个可选值INCLUDE包括、IGNORE忽略其中要注意
标识作为条件的实体对象中一个属性值条件值为 Null 时是否参与过滤当该选项值是 INCLUDE 时表示仍参与过滤会匹配数据库表中该字段值是 Null 的记录若为 IGNORE 值表示不参与过滤。
复制代码
//提供一个默认实现方法忽略 NULL 属性
default ExampleMatcher withIgnoreNullValues() {return withNullHandler(NullHandler.IGNORE);
}
//把 NULL 属性值作为查询条件
default ExampleMatcher withIncludeNullValues() {return withNullHandler(NullHandler.INCLUDE);
}到这里看一下把 NULL 属性值作为查询条件会执行什么样的 SQL
复制代码
Hibernate: select useraddres0_.id as id1_2_, useraddres0_.address as address2_2_, useraddres0_.user_id as user_id3_2_ from user_address useraddres0_ inner join user user1_ on useraddres0_.user_iduser1_.id where (user1_.id is null) and (user1_.update_date is null) and user1_.age20 and (user1_.create_date is null) and lower(user1_.name)? and (lower(user1_.email) like ? escape ?) and (user1_.sex is null) and (lower(useraddres0_.address) like ? escape ?) and (useraddres0_.id is null) limit ?这样就会导致我们一条数据都查不出来了。 忽略某些 Paths不参加查询条件
复制代码
//忽略某些属性列表不参与查询过滤条件。
ExampleMatcher withIgnorePaths(String... ignoredPaths);字符串字段默认的匹配规则
复制代码
ExampleMatcher withStringMatcher(StringMatcher defaultStringMatcher);关于默认字符串的匹配方式枚举类型有 6 个可选值DEFAULT默认效果同 EXACT、EXACT相等、STARTING开始匹配、ENDING结束匹配、CONTAINING包含模糊匹配、REGEX正则表达式。 字符串匹配规则我们和 JPQL 对应到一起举例如下表所示 相关代码如下
复制代码
ExampleMatcher withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher);这里显示的是指定某些属性的匹配规则我们看一下 GenericPropertyMatcher 是什么东西它都提供了哪些方法。
如下图基本可以看出来都是针对字符串属性提供的匹配规则也就是可以通过这个方法定制不同属性的 StringMatcher 规则。 到这里语法部分我们就学习完了下面看一个完整的例子感受一下。
完整的例子
下面是一个关于咱们上面所说的暴露的方法的使用的例子你可以跟着我的步骤自己动手练习一下。
复制代码
//创建匹配器即如何使用查询条件
ExampleMatcher exampleMatcher ExampleMatcher//采用默认and的查询方式.matchingAll()//忽略大小写.withIgnoreCase()//忽略所有null值的字段.withIgnoreNullValues().withIgnorePaths(id,createDate)//默认采用精准匹配规则.withStringMatcher(ExampleMatcher.StringMatcher.EXACT)//级联查询字段user.email采用字符前缀匹配规则.withMatcher(user.email, ExampleMatcher.GenericPropertyMatchers.startsWith())//特殊指定address字段采用后缀匹配.withMatcher(address, ExampleMatcher.GenericPropertyMatchers.endsWith());
PageUserAddress u userAddressRepository.findAll(Example.of(address,exampleMatcher), PageRequest.of(0,2));这时候可能会有同学问了我是怎么知道默认值的呢我们直接看类的构造方法就可以了如下所示 从源码中我们可以看到实现类的构造方法只有一个就是“赋值默认”的方式。下面我整理了一些在使用这个语法时需要考虑的细节。
ExampleExceutor 使用中需要考虑的因素
Null 值的处理当某个条件值为 Null 时是应当忽略这个过滤条件还是应当去匹配数据库表中该字段值是 Null 的记录呢忽略某些属性值一个实体对象有许多个属性是否每个属性都参与过滤是否可以忽略某些属性不同的过滤方式同样是作为 String 值可能“姓名”希望精确匹配“地址”希望模糊匹配如何做到
那么接下来我们分析一下源码看看其原理说了这么半天它到底和 JpaSpecificationExecutor 什么关系呢我们接着看。
QueryByExampleExecutor 源码分析
怎么分析源码也很简单我们看一下上面的我们 findAll 的方法调用之处。 从而找到 findAll 方法的实现类如下所示 通过 Debug 断点我们可以看到我们刚才组合出来的 Example 对象这个时候被封装成了 ExampleSpecification 对象那么我们接着往下看方法里面的关键内容。
复制代码
TypedQueryS query getQuery(new ExampleSpecification(example, escapeCharacter), probeType, pageable);getQuery 方法是创建 Query 的关键因为它里面做了条件的转化逻辑。那么我们再看一下参数 ExampleSpecification 的源码发现它是接口 Specification 的实现类并且是非公开的实现类可以通过接口对外暴露 and、or、not、where 等组合条件的查询条件。 我们接着看上面的 getQuery 方法的实现可以看到接收的参数是 Specification接口所以不用关心实现类是什么。 我们接着再看这个断点的 getQuery 方法 里面有一段代码会调用 applySpecificationToCriteria 生成 root并由 Root 作为参数生成 Query从而交给 EMEntityManager进行查询。
我们再来看一下关键的 applySpecificationToCriteria 方法。 根据 Specification 调用 toPredicate 方法生成 Predicate从而实现查询需求。
现在我们已经对 QueryByExampleExecutor 的用法和实现原理基本掌握了我们再来看一个十分相似的接口JpaSpecificationExecutor 是干什么用的。
JpaSpecificationExecutor 接口结构
正如我们开篇提到的【图一Repository 类图】JpaSpecificationExecutor 是 JPA 里面的另一个接口分支。我们先来看看它的基本语法。 我们通过查看 JpaSpecificationExecutor 的 Structure 图会发现方法就有这么几个细心的同学这个时候会发现它的参数 Specification正是我们分析 QueryByExampleExecutor 的原理时候使用的 Specification。
那么 JpaSpecificationExecutor 帮我们解决了哪些问题呢
JpaSpecificationExecutor 解决了哪些问题
我们通过 QueryByExampleExecutor 的使用方法和原理分析不难发现JpaSpecificationExecutor 的查询条件 Specification 十分灵活可以帮我们解决动态查询条件的问题正如 QueryByExampleExecutor 的用法一样它提供的 Criteria API 的使用封装可以用于动态生成 Query 来满足我们业务中的各种复杂场景既然QueryByExampleExecutor 能利用 Specification 封装成框架我们是不是也可以利用 JpaSpecificationExecutor 封装成框架呢这样就学会了举一反三。