一、OverView

​ 在前面已经介绍过了 SpringBoot 整合 JPA 了,但是一般在公司业务中,如果出现很多次多条件搜索查询的情况,那么就会用大量冗余代码了;再结合注解的思想。本次将公共部分提取出来,并用注解的方式进行操作,可以参考前面使用注解进行 AOP 处理日志,也符合了 SpringBoot 的思想。

二、BaseQuery

QueryWord

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface QueryWord {

// 数据库中字段名,默认为空字符串,则Query类中的字段要与数据库中字段一致
String column() default "";

// equal, like, gt, lt...
MatchType func() default MatchType.equal;

// object是否可以为null
boolean nullable() default false;

// 字符串是否可为空
boolean empty() default false;

// between...and... 查询语句标识, 0时间 1数字类型
BetweenType type() default BetweenType.datetime;
}

注:注解类,只要在要查询的属性上加上相应的注解即可

MatchType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public enum MatchType {

equal, // filed = value

// 下面四个用于Number类型的比较
gt, // filed > value
ge, // field >= value
lt, // field < value
le, // field <= value

notEqual, // field != value
like, // field like value
notLike, // field not like value
between, // between value1 and value2 ,Type is Date

// 下面四个用于可比较类型(Comparable)的比较
greaterThan, // field > value
greaterThanOrEqualTo, // field >= value
lessThan, // field < value
lessThanOrEqualTo // field <= value

}

注:枚举类,可以对比注释进行理解

BetweenType

1
2
3
4
5
6
7
public enum  BetweenType {

datetime,
number_long,
number_integer

}

注:对应上面 QueryWord中的 BetweenType ,可以对时间或数字之间的属性进行注解

BaseQuery

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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
public abstract class BaseQuery<T> {

// 第多少页 + 页大小
private int pageNum = 0;
private int pageSize = 10;

//
private static Map<Class, List<Field>> fieldCache = new HashMap<>();

// 动态查询
public abstract Specification<T> toSpec();

// 分页
public Pageable toPageable() {
return PageRequest.of(pageNum, pageSize);
}
// 分页 + 排序
public Pageable toPageable(Sort sort) {
return PageRequest.of(pageNum, pageSize, sort);
}

// 动态查询 and 连接
protected Specification<T> toSpecWithAnd() {
return this.toSpecWithLogicType("and");
}
// 动态查询 or 连接
protected Specification<T> toSpecWithOr() {
return this.toSpecWithLogicType("or");
}

// logicType = or | and
@SuppressWarnings({ "rawtypes", "unchecked" })
private Specification<T> toSpecWithLogicType(final String logicType) {
final BaseQuery outerThis = this;
// 封装条件查询对象 Specification
Specification<T> specification = new Specification<T>() {
@Override
// Root 用于获取属性字段,CriteriaQuery可以用于简单条件查询,CriteriaBuilder 用于构造复杂条件查询
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Class clazz = outerThis.getClass();
// 判断缓存中是否已经存在,存在不需要再次生成,不存在需要重新生成
List<Field> fields = fieldCache.get(clazz);
if (fields == null) {
// 获取查询类Query的所有字段,包括父类字段
fields = getAllFieldsWithRoot(clazz);
fieldCache.put(clazz, fields);
}
List<Predicate> predicates = new ArrayList<>(fields.size());
for (Field field : fields) {
// 获取字段上的 @QueryWord 注解
QueryWord qw = field.getAnnotation(QueryWord.class);
if (qw == null) {
continue;
}
// 获取字段名
String column = qw.column();
//如果主注解上 column 为默认值"",则以 field 为准
if (column.equals("")) {
column = field.getName();
}
field.setAccessible(true);
try {
// nullable
Object value = field.get(outerThis);
// 如果值为 null,注解未标注 nullable,跳过
if (value == null && !qw.nullable()) {
continue;
}
// can be empty
if (value != null && String.class.isAssignableFrom(value.getClass())) {
String s = (String) value;
//如果值为"",且注解未标注 empty ,跳过
if (s.equals("") && !qw.empty()) {
continue;
}
}
//通过注解上 func 属性,构建路径表达式
Path path = root.get(column);
switch (qw.func()) {
case equal:
predicates.add(cb.equal(path, value));
break;
case like:
predicates.add(cb.like(path, "%" + value + "%"));
break;
case gt:
predicates.add(cb.gt(path, (Number) value));
break;
case lt:
predicates.add(cb.lt(path, (Number) value));
break;
case ge:
predicates.add(cb.ge(path, (Number) value));
break;
case le:
predicates.add(cb.le(path, (Number) value));
break;
case notEqual:
predicates.add(cb.notEqual(path, value));
break;
case notLike:
predicates.add(cb.notLike(path, "%" + value + "%"));
break;
case greaterThan:
predicates.add(cb.greaterThan(path, (Comparable) value));
break;
case greaterThanOrEqualTo:
predicates.add(cb.greaterThanOrEqualTo(path, (Comparable) value));
break;
case lessThan:
predicates.add(cb.lessThan(path, (Comparable) value));
break;
case lessThanOrEqualTo:
predicates.add(cb.lessThanOrEqualTo(path, (Comparable) value));
break;
case between:
switch (qw.type()) {
case datetime:
List<Date> dateList = (List<Date>) value;
predicates.add(cb.between(path, dateList.get(0), dateList.get(1)));
break;
case number_long:
List<Long> longList = (List<Long>) value;
predicates.add(cb.between(path, longList.get(0), longList.get(1)));
break;
case number_integer:
List<Integer> integerList = (List<Integer>) value;
predicates.add(cb.between(path, integerList.get(0), integerList.get(1)));
break;
}
}
} catch (Exception e) {
continue;
}
}
Predicate p = null;
if (logicType == null || logicType.equals("") || logicType.equals("and")) {
p = cb.and(predicates.toArray(new Predicate[predicates.size()])); // and 连接
} else if (logicType.equals("or")) {
p = cb.or(predicates.toArray(new Predicate[predicates.size()])); // or 连接
}
return p;
}
};
return specification;
}

//获取类 clazz 的所有 Field,包括其父类的 Field
private List<Field> getAllFieldsWithRoot(Class<?> clazz) {
List<Field> fieldList = new ArrayList<>();
Field[] dFields = clazz.getDeclaredFields(); //获取本类所有字段
if (null != dFields && dFields.length > 0) {
fieldList.addAll(Arrays.asList(dFields));
}
// 若父类是 Object,则直接返回当前 Field 列表
Class<?> superClass = clazz.getSuperclass();
if (superClass == Object.class) {
return Arrays.asList(dFields);
}
// 递归查询父类的 Field 列表
List<Field> superFields = getAllFieldsWithRoot(superClass);
if (null != superFields && !superFields.isEmpty()) {
for (Field field : superFields) {
if (!fieldList.contains(field)) {
fieldList.add(field);
}
}
}
return fieldList;
}

public int getPageIndex() {
return pageNum;
}

public void setPageIndex(int pageIndex) {
this.pageNum = pageIndex;
}

public int getPageSize() {
return pageSize;
}

public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}


}

注:最关键的类

  • 所有后面要查询的类都要继承它
  • 简单的理解:通过反射获取注解上的字段信息,构造相应的搜索条件

三、Test

StudentQuery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class StudentQuery extends BaseQuery{

@QueryWord(column = "id", func = MatchType.equal)
private Long id;

@QueryWord(func = MatchType.like)
private String name;

// 省略 set / get

@Override
public Specification toSpec() {
return super.toSpecWithOr();
}
}

在这个查询中,使用的是 or 查询,测试类如下:

Test

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void find8() {
StudentQuery studentQuery = new StudentQuery();
studentQuery.setId(1L);
studentQuery.setName("er");
studentQuery.setPageIndex(0);
studentQuery.setPageSize(10);
Page page = studentRepository.findAll(studentQuery.toSpec(), studentQuery.toPageable());
for (Object o : page.getContent()) {
System.out.println(o);
}
}

注:构造的条件是 id = 1的,或者 name = ? er ? 的 student

结果

image-20200914200754861

通过这个例子简单捋一下整个过程:

  1. 通过 studentQuery.toSpec() 调用父类 BaseQuery;
  2. 在 BaseQuery 中,通过反射获得打了相应注解上的字段信息;
  3. 将字段信息加到搜索条件中,进行 or 或者 and 连接

注:如果你的业务中只用了很少的条件或者只用极个别的多条件查询,可以不用这个工具类

评论