JPA(五)-高级查询
参考文献
- 拉钩教育–Spring Data JPA原理与实战
QueryByExampleExecutor
- QueryByExampleExecutor(QBE)是一种用户友好的查询技术,具有简单的接口,它允许动态查询创建,并且不需要编写包含字段名称的查询。
QBE 的基本语法
1 | public interface QueryByExampleExecutor<T> { |
1 |
|
1 | Example 语法详解 |
- 三个类:Probe、ExampleMatcher 和 Example,分别做如下解释:
- Probe:这是具有填充字段的域对象的实际实体类,即查询条件的封装类(又可以理解为查询条件参数),必填。
- ExampleMatcher:ExampleMatcher 有关如何匹配特定字段的匹配规则,它可以重复使用在多个实例中,必填。
- Example:Example 由 Probe 探针和 ExampleMatcher 组成,它用于创建查询,即组合查询参数和参数的匹配规则。
- 通过 Example 的源码,我们发现想创建 Example 的话,只有两个方法
- static
<T>
Example<T>
of(T probe):需要一个实体参数,即查询的条件。而里面的 ExampleMatcher 采用默认的 ExampleMatcher.matching(); 表示忽略 Null,所有字段采用精准匹配。 - static
<T>
Example<T>
of(T probe, ExampleMatcher matcher):需要两个参数构建 Example,也就表示了 ExampleMatcher 自由组合规则,正如我们上面的测试用例里面的代码一样。
- static
ExampleMatcher
1 | //默认matching方法 |
ExampleMatcher 语法暴露的方法
忽略大小写
1 | //默认忽略大小写的方式,默认 False。 |
暴露的 Null 值处理方式如下:
1 | ExampleMatcher withNullHandler(NullHandler nullHandler); |
忽略某些 Paths,不参加查询条件
1 | //忽略某些属性列表,不参与查询过滤条件。 |
字符串字段默认的匹配规则
1 | ExampleMatcher withStringMatcher(StringMatcher defaultStringMatcher); |
示例
1 | //创建匹配器,即如何使用查询条件 |
-
ExampleExceutor 使用中需要考虑的因素
- Null 值的处理:当某个条件值为 Null 时,是应当忽略这个过滤条件,还是应当去匹配数据库表中该字段值是 Null 的记录呢?
- 忽略某些属性值:一个实体对象,有许多个属性,是否每个属性都参与过滤?是否可以忽略某些属性?
- 不同的过滤方式:同样是作为 String 值,可能“姓名”希望精确匹配,“地址”希望模糊匹配,如何做到?
JpaSpecificationExecutor 使用案例
1 |
|
1 | public interface JpaSpecificationExecutor<T> { |
Root<User> root
- 代表了可以查询和操作的实体对象的根,如果将实体对象比喻成表名,那 root 里面就是这张表里面的字段,而这些字段只是 JPQL 的实体字段而已。我们可以通过里面的 Path get(String attributeName),来获得我们想要操作的字段。
CriteriaQuery<?> query
- 代表一个 specific 的顶层查询对象,它包含着查询的各个部分,比如 select 、from、where、group by、order by 等。CriteriaQuery 对象只对实体类型或嵌入式类型的 Criteria 查询起作用。简单理解为,它提供了查询 ROOT 的方法。
CriteriaBuilder cb
-
CriteriaBuilder 是用来构建 CritiaQuery 的构建器对象,其实就相当于条件或者条件组合,并以 Predicate 的形式返回.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162package com.example.jpa.example1.spe;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.*;
public class MySpecification<Entity> implements Specification<Entity> {
private SearchCriteria criteria;
public MySpecification (SearchCriteria criteria) {
this.criteria = criteria;
}
/**
* 实现实体根据不同的字段、不同的Operator组合成不同的Predicate条件
*
* @param root must not be {@literal null}.
* @param query must not be {@literal null}.
* @param builder must not be {@literal null}.
* @return a {@link Predicate}, may be {@literal null}.
*/
public Predicate toPredicate(Root<Entity> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
if (criteria.getOperation().compareTo(Operator.GT)==0) {
return builder.greaterThanOrEqualTo(
root.<String> get(criteria.getKey()), criteria.getValue().toString());
}
else if (criteria.getOperation().compareTo(Operator.LT)==0) {
return builder.lessThanOrEqualTo(
root.<String> get(criteria.getKey()), criteria.getValue().toString());
}
else if (criteria.getOperation().compareTo(Operator.LK)==0) {
if (root.get(criteria.getKey()).getJavaType() == String.class) {
return builder.like(
root.<String>get(criteria.getKey()), "%" + criteria.getValue() + "%");
} else {
return builder.equal(root.get(criteria.getKey()), criteria.getValue());
}
}
return null;
}
}
package com.example.jpa.example1.spe;
import lombok.*;
/**
* @author jack,实现不同的查询条件,不同的操作,针对Value;
*/
public class SearchCriteria {
private String key;
private Operator operation;
private Object value;
}
package com.example.jpa.example1.spe;
public enum Operator {
/**
* 等于
*/
EQ("="),
/**
* 等于
*/
LK(":"),
/**
* 不等于
*/
NE("!="),
/**
* 大于
*/
GT(">"),
/**
* 小于
*/
LT("<"),
/**
* 大于等于
*/
GE(">=");
Operator(String operator) {
this.operator = operator;
}
private String operator;
}
/**
* 测试自定义的Specification语法
*/
public void givenLast_whenGettingListOfUsers_thenCorrect() {
MySpecification<User> name =
new MySpecification<User>(new SearchCriteria("name", Operator.LK, "jack"));
MySpecification<User> age =
new MySpecification<User>(new SearchCriteria("age", Operator.GT, 2));
List<User> results = userRepository.findAll(Specification.where(name).and(age));
System.out.println(results.get(0).getName());
}
先创建一个 Controller,用来接收 search 这样的查询条件:类似 userssearch=lastName:doe,age>25 的参数。
public class UserController {
private UserRepository repo;
public List<User> search( { String search)
Specification<User> spec = new SpecificationsBuilder<User>().buildSpecification(search);
return repo.findAll(spec);
}
}
Controller 里面非常简单,利用 SpecificationsBuilder 生成我们需要的 Specification 即可。
package com.example.jpa.example1.spe;
import com.example.jpa.example1.User;
import org.springframework.data.jpa.domain.Specification;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 处理请求参数
* @param <Entity>
*/
public class SpecificationsBuilder<Entity> {
private final List<SearchCriteria> params;
//初始化params,保证每次实例都是一个新的ArrayList
public SpecificationsBuilder() {
params = new ArrayList<SearchCriteria>();
}
//利用正则表达式取我们search参数里面的值,解析成SearchCriteria对象
public Specification<Entity> buildSpecification(String search) {
Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
Matcher matcher = pattern.matcher(search + ",");
while (matcher.find()) {
this.with(matcher.group(1), Operator.fromOperator(matcher.group(2)), matcher.group(3));
}
return this.build();
}
//根据参数返回我们刚才创建的SearchCriteria
private SpecificationsBuilder with(String key, Operator operation, Object value) {
params.add(new SearchCriteria(key, operation, value));
return this;
}
//根据我们刚才创建的MySpecification返回所需要的Specification
private Specification<Entity> build() {
if (params.size() == 0) {
return null;
}
List<Specification> specs = params.stream()
.map(MySpecification<User>::new)
.collect(Collectors.toList());
Specification result = specs.get(0);
for (int i = 1; i < params.size(); i++) {
result = Specification.where(result)
.and(specs.get(i));
}
return result;
}
}
EntityManager
-
获得 EntityManager 的方式:通过 @PersistenceContext 注解。
-
将 @PersistenceContext 注解标注在 EntityManager 类型的字段上,这样得到的 EntityManager 就是容器管理的 EntityManager。由于是容器管理的,所以我们不需要、也不应该显式关闭注入的 EntityManager 实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class UserRepositoryTest {
//利用该方式获得entityManager
private EntityManager entityManager;
private UserRepository userRepository;
/**
* 测试entityManager用法
*
* @throws JsonProcessingException
*/
public void testEntityManager() throws JsonProcessingException {
//测试找到一个User对象
User user = entityManager.find(User.class,2L);
Assertions.assertEquals(user.getAddresses(),"shanghai");
//我们改变一下user的删除状态
user.setDeleted(true);
//merger方法
entityManager.merge(user);
//更新到数据库里面
entityManager.flush();
//再通过createQuery创建一个JPQL,进行查询
List<User> users = entityManager.createQuery("select u From User u where u.name=?1")
.setParameter(1,"jack")
.getResultList();
Assertions.assertTrue(users.get(0).getDeleted());
}
}
-
@EnableJpaRepositories
1 | public EnableJpaRepositories { |
通过 @EnableJpaRepositories 定义默认的 Repository 的实现类
1 | 第一步:正如上面我们讲的利用 指定 repositoryBaseClass,代码如下: |
-
实际应用场景
-
首先肯定是我们做框架的时候、解决一些通用问题的时候,如逻辑删除,正如我们上面的实例所示的样子。
-
在实际生产中经常会有这样的场景:对外暴露的是 UUID 查询方法,而对内暴露的是 Long 类型的 ID,这时候我们就可以自定义一个 FindByIdOrUUID 的底层实现方法,可以选择在自定义的 Respository 接口里面实现。
-
Defining Query Methods 和 @Query 满足不了我们的查询,但是我们又想用它的方法语义的时候,就可以考虑实现不同的 Respository 的实现类,来满足我们不同业务场景的复杂查询。
1
2
3
4
public class User implements Serializable {
....
}
-
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 HoleLin's Blog!