类函数式的sql生成工具类的封装
前言
自从正式工作以来,公司一直用的是Spring 原生的JDBC Template以及在其上封装的扩展的一些小工具, 而摈弃了Mybatis、ibatis等ORM框架。总的来说,这种做法对于开发效率来说提高不少,由于真正的查库操作不会直接穿透到Mysql,所以抗压性也没有太大的问题。
问题
虽说直接使用原生的JDBC Template比较方便,但是构造参数条件,以及生成查询语句过于简单粗暴,导致代码不简洁,复用度也不高。举个例子,根据特定的条件查询用户:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23StringBuffer sqlBuilder = new StringBuffer("select * from user where ");
Map<String, Object> paramsMap = new HashMap<>();
if (userId != null) {
sqlBuilder.append("user_id=:userId");
paramsMap.put("userId", userId);
}
if (batchNumber != null) {
sqlBuilder.append("batch_number=:batchNumber");
paramsMap.put("batchNumber", batchNumber);
}
if (status != null) {
sqlBuilder.append("status=:status");
paramsMap.put("stats", status);
}
if (page > 0 && count > 0) {
sqlBuilder.append(" limit ")
.append(page * count)
.append(", ")
.append(count);
}
//生成sql语句
sqlBuilder.toString();
可以看到上面的代码大部分在重复同样的逻辑,20多行的代码仅仅只是在构造sql语句以及收集参数,而且这些重复的代码将充斥项目所有的DAO层,导致代码非常不整洁。维护困难。
sql生成工具类封装
秉承着恶心重复的代码要重构抽象的态度,对sql生成的步骤,做了一次简单的封装:
- 先介绍接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public interface Order {
Where desc();
Where asc();
}
public interface Where {
Where ifPresent(Object value, String sql);
Order orderBy(String field);
Where limit(int begin, int end);
Where in(List<Object> values, String sql);
String sql();//构造的sql
Map<String, Object> params();//参数值
}
根据sql语句的特点,抽出子句部分的生成单独构造,涵盖Where, in(暂未实现),limit 以及 order 排序规则。
- 来看看接口的实现
将所有相关的类通过内部静态类封装入SqlWhereBuffer类中。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
93public class SqlWhereBuffer {
public static SqlWhereBuffer.Where builder() {
return new Builder();
}
static class Builder implements SqlWhereBuffer.Order, SqlWhereBuffer.Where {
private List<String> sql = new ArrayList<>();
private Map<String, Object> params = new HashMap<>();
private String orderBy = " ";
private String limit = " ";
private String in = " ";
private List<SqlMapperPlugin> sqlMapperPlugins;
public Builder() {
//添加基本类型默认的插件
sqlMapperPlugins = Lists.newArrayList();
this.mapperPlugins(SqlMapperPlugin.EnumSqlPlugin)
.mapperPlugins(SqlMapperPlugin.BooleanSqlPlugin);
}
public Builder mapperPlugins(SqlMapperPlugin sqlMapperPlugin) {
if (CollectionUtils.isEmpty(sqlMapperPlugins)) {
sqlMapperPlugins = Lists.newArrayList();
}
sqlMapperPlugins.add(sqlMapperPlugin);
return this;
}
@Override
public SqlWhereBuffer.Where ifPresent(Object value, String sql) {
Optional.ofNullable(value)
.ifPresent(val -> {
this.sql.add(sql);
Pattern pattern = Pattern.compile(":([a-z,A-Z,\\w,_]*)"); //固定的sql参数模式
Matcher matcher = pattern.matcher(sql);
if (!matcher.find()) {
throw new IllegalArgumentException(sql + " don't include :name");
}
Optional<SqlMapperPlugin> mapper = sqlMapperPlugins.stream()
.filter(sqlMapperPlugin -> sqlMapperPlugin.test(val))
.findFirst();
if (mapper.isPresent()) {
params.putAll(mapper.get()
.getParams(matcher.group(1), value));
} else {
//匹配不到走默认的插件
params.put(matcher.group(1), value);
}
});
return this;
}
@Override
public SqlWhereBuffer.Order orderBy(String field) {
orderBy += "ORDER BY " + field;
return this;
}
@Override
public SqlWhereBuffer.Where limit(int offset, int count) {
limit += "limit " + offset + "," + count;
return this;
}
@Override
public String sql() {
if (sql.isEmpty() || params.isEmpty()) {
return "";
} else {
return " WHERE " + sql.stream()
.collect(Collectors.joining(" AND ")) + orderBy + limit;
}
}
@Override
public ImmutableMap<String, Object> params() {
return ImmutableMap.copyOf(params);
}
@Override
public SqlWhereBuffer.Where desc() {
orderBy += " DESC";
return this;
}
@Override
public SqlWhereBuffer.Where asc() {
return this;
}
}
}
静态类Builder实现了SqlWhereBuffer.Order, SqlWhereBuffer.Where这俩个接口的方法。ifPresent是主入口。流程如下:1
2
31、在ifPresent中参数先进行非空判断,如果是空则直接过滤掉;
2、然后通过一个正则表达式(**:([a-z,A-Z,\\w,_]*)**)对参数sql进行规则校验,如果不符合也将过滤;
3、最后value通过一系列的自定义的注册插件的匹配判断,得出sql以及params。
- 关于自定义插件,本没想做这么复杂,然而在实际的使用过程中,JDBC Template对Enum的支持不够…不过想也是,Enum大多是自定义的,没法做到一套约定的接口满足所有需求。于是封装了一个插件类,方便以后扩展:
1 | public static class SqlMapperPlugin { |
主要用到了java8中新增的Predicate接口,用于判断value的Class类型,以及自己定义了一个FunctionalInterface 用于获取查询参数。这里先实现了EnumSqlPlugin,BooleanSqlPlugin俩个插件,之后有其他类型的需求,也可以通过类似的形式加入。对EnumSqlPlugin实现进行解释:1
2
3
4//定义枚举插件
static SqlMapperPlugin EnumSqlPlugin = of(Enum.class).paramValue((value, sql) ->
Collections.singletonMap(sql, getEnumValue(value))
);
通过定义的of方法定义赋值Predicate,作为判断value是否为指定类型的方法。1
2
3
4
5public static SqlMapperPlugin.MapperPluginsBuilder of(Class clazz) {
return of((pd) -> {
return clazz.isAssignableFrom(pd.getClass());
});
}
of方法返回了MapperPluginsBuilder类,紧接着定义函数式接口paramValue的实现:1
Collections.singletonMap(sql, getEnumValue(value))
最后返回EnumSqlPlugin对应的新实例:1
return new SqlMapperPlugin(this.predicate, paramValue);
使用的时候,在Builder的构造函数中注入默认的插件即可,如日后有扩展,也可调用mapperPlugins方法动态加入:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 private List<SqlMapperPlugin> sqlMapperPlugins;
public Builder() {
//添加基本类型默认的插件
sqlMapperPlugins = Lists.newArrayList();
this.mapperPlugins(SqlMapperPlugin.EnumSqlPlugin)
.mapperPlugins(SqlMapperPlugin.BooleanSqlPlugin);
}
public Builder mapperPlugins(SqlMapperPlugin sqlMapperPlugin) {
if (CollectionUtils.isEmpty(sqlMapperPlugins)) {
sqlMapperPlugins = Lists.newArrayList();
}
sqlMapperPlugins.add(sqlMapperPlugin);
return this;
}
工具的使用
现在,我们可以使用封装好的工具了:1
2
3
4
5
6
7SqlWhereBuffer.Where where = SqlWhereBuffer.builder()
.ifPresent(batchNumber, "batch_number=:batchNumber")
.ifPresent(status, "upload_result=:uploadResult")
.ifPresent(userId, "user_id=:userId")
.limit(page * count, count)
.orderBy("id")
.asc();
这样,代码变得整洁多了,也符合java8函数式风格的效果。