前言
从个人角度来说 我更加喜欢jpa 因为是正儿八经的orm框架 很多时候 只需要处理java代码即可 不需要去梭sql
虽然真正去梭sql 性能更好 也更加灵活 可以使用各种各样的操作 如果项目是做一些复杂的查询 我觉得mybatis 或者直接jdbc之类的会更加合适
此示例 只面向懂一部分jpa的工程师 如果都不知道jpa是啥 建议还是先看看官方文档
基础示例
jpa 做条件查询的时候 要么直接写queryDSL 要么构建example(简单条件可以选择使用这个) 或者构建specification
在复杂条件的时候 肯定是使用specification
- specification 规则 是多个Predicate 组合成的访问规则
- Predicate 相当于 查询语句的 where之后的各种条件 如aaa=value xxx>value
- Root 对象映射的根对象 相当于一个起点 如 Staff实体 中间有个name属性 那么需要判断name等于xxx 需要使用Root.get(“name”)来获取Path 如何在这个Path上构建Predicate Root代表本实体Staff
1 | package com.ming; |
高度封装示例
思路
- 使用注解标明字段 增加规则方式 和应用规则的entity 字段
- 编写工具类根据注解 构建specification
- 将Repository 继承 JpaSpecificationExecutor 使用specification去访问数据
注解
- MyJpaSpecifications 注解
1 | package com.ming.base.orm.annotatioin; |
- JpaOperator 枚举类
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
28package com.ming.base.orm;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
public enum JpaOperator {
EQ,
LIKE,
NOT_LIKE,
GT,
LT,
GTE,
LTE,
IN,
NEQ,
OR;
}
根据注解构建specification
- SpecificationUtils
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
122package com.ming.base.orm;
import com.ming.base.CodeException;
import com.ming.base.ResponseBody;
import com.ming.base.orm.annotatioin.MyJpaSpecifications;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.ReflectionUtils;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 构建查询对Specification进行封装 用searchfilter enum进行封装
*
* @author ming
* @date 2020-04-21 09:25:33
*/
public class SpecificationUtils {
/**
* 根据class 获取字段 和字段的注解 和值 构建 条件
*
* @author ming
* @date 2020-04-21 13:26:13
*/
public static <T, V> Specification<T> buildSpecificationByMyJpaSpecifications(Class<V> clazz, V val) {
return (root, query, builder) -> {
List<Predicate> predicates = new ArrayList<Predicate>();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(Boolean.TRUE);
//获取注解
MyJpaSpecifications myJpaSpecifications = field.getAnnotation(MyJpaSpecifications.class);
if (Objects.isNull(myJpaSpecifications)) {
//如果没有注解 忽略此字段 不进行构建处理
continue;
}
//如果没有输入实体字段 默认为当前属性字段的名称
String nameStr = myJpaSpecifications.entityField();
if (StringUtils.isEmpty(nameStr)) {
nameStr = field.getName();
}
String[] names = StringUtils.split(nameStr, ".");
Path expression = root.get(names[0]);
for (int i = 1; i < names.length; i++) {
expression = expression.get(names[i]);
}
//in 和or 中需要的一个中间变量 用来将filter.value放入数组
Object[] objects = new Object[1];
switch (myJpaSpecifications.operator()) {
case EQ:
predicates.add(builder.equal(expression, ReflectionUtils.getField(field, val)));
break;
case LIKE:
predicates.add(builder.like(expression, "%" + ReflectionUtils.getField(field, val) + "%"));
break;
case NOT_LIKE:
predicates.add(builder.notLike(expression, "%" + ReflectionUtils.getField(field, val) + "%"));
break;
case GT:
predicates.add(builder.greaterThan(expression, (Comparable) ReflectionUtils.getField(field, val)));
break;
case LT:
predicates.add(builder.lessThan(expression, (Comparable) ReflectionUtils.getField(field, val)));
break;
case GTE:
predicates.add(builder.greaterThanOrEqualTo(expression, (Comparable) ReflectionUtils.getField(field, val)));
break;
case LTE:
predicates.add(builder.lessThanOrEqualTo(expression, (Comparable) ReflectionUtils.getField(field, val)));
break;
case IN:
//因为spring data jpa 本身没有对数组进行判断 传入数组的话会失败 所以在此进行是否是数组的判断
//因为expression。in参数是不定参数 理论上是可以传入数组 但是直接传入object不能判断是否为数组
//把他当成一个参数 而不是需要的数组参数
Object filterValue = ReflectionUtils.getField(field, val);
if (filterValue.getClass().isArray()) {
objects = (Object[]) filterValue;
} else {
objects[0] = filterValue;
}
predicates.add(expression.in(objects));
break;
case NEQ:
predicates.add(builder.notEqual(expression, ReflectionUtils.getField(field, val)));
break;
case OR:
List<Predicate> preList = new ArrayList<>();
Object obj = ReflectionUtils.getField(field, val);
if (obj.getClass().isArray()) {
objects = (Object[]) obj;
for (Object object : objects) {
Predicate pp = builder.like(expression, "%" + object + "%");
preList.add(pp);
}
} else {
preList.add(builder.like(expression, "%" + obj + "%"));
}
Predicate[] pres = preList.toArray(new Predicate[0]);
predicates.add(builder.or(pres));
break;
default:
throw new CodeException(ResponseBody.CodeEnum.DATA_NOT_FOUND, "没有" + myJpaSpecifications.operator().name() + "操作");
}
}
if (CollectionUtils.notEmpty(predicates)) {
return builder.and(predicates.toArray(new Predicate[0]));
}
return builder.conjunction();
};
}
}
使用实例
定义查询对象 xxxQuery 使用 @MyJpaSpecifications 来标识改字段如何构建查询规则
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
44package com.ming.common.controller.query;
import com.ming.base.mvc.BaseQuery;
import com.ming.base.orm.JpaOperator;
import com.ming.base.orm.SpecificationUtils;
import com.ming.base.orm.annotatioin.MyJpaSpecifications;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.data.jpa.domain.Specification;
import java.time.LocalDateTime;
/**
* 查询api耗时log分页条件
*
* @author ming
* @date 2020-04-21 10:49:23
*/
public class ApiConsumingLogPageQuery implements BaseQuery {
private String httpMethod;
private String uri;
private Long minTimeConsuming;
private Long maxTimeConsuming;
private LocalDateTime startTime;
private LocalDateTime endTime;
public <T> Specification<T> toSpecification() {
return SpecificationUtils.buildSpecificationByMyJpaSpecifications(ApiConsumingLogPageQuery.class, this);
}
}调用findAll函数
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
33package com.ming.common.service.impl;
import com.ming.common.controller.query.ApiConsumingLogPageQuery;
import com.ming.common.entity.ApiConsumingLog;
import com.ming.common.repository.ApiConsumingLogRepository;
import com.ming.common.service.ApiConsumingLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
/**
* api耗时日志 服务实现
*
* @author ming
* @date 2020-04-21 11:26:34
*/
public class ApiConsumingLogServiceImpl implements ApiConsumingLogService {
private ApiConsumingLogRepository apiConsumingLogRepository;
public Page<ApiConsumingLog> page(Pageable pageable, ApiConsumingLogPageQuery apiConsumingLogPageQuery) {
return apiConsumingLogRepository.findAll(apiConsumingLogPageQuery.toSpecification(), pageable);
}
}
注意
repository必须继承JpaSpecificationExecutor 才能使用 Page
总结
使用Specification去访问复杂条件的对象 如果全手工构建是比较麻烦的
使用注解 降低操作难度 会让jpa用起来更加顺畅