前言

自从正式工作以来,公司一直用的是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
23
StringBuffer 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
    19
    public 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
    93
    public 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
3
1、在ifPresent中参数先进行非空判断,如果是空则直接过滤掉;
2、然后通过一个正则表达式(**:([a-z,A-Z,\\w,_]*)**)对参数sql进行规则校验,如果不符合也将过滤;
3、最后value通过一系列的自定义的注册插件的匹配判断,得出sql以及params。

  • 关于自定义插件,本没想做这么复杂,然而在实际的使用过程中,JDBC Template对Enum的支持不够…不过想也是,Enum大多是自定义的,没法做到一套约定的接口满足所有需求。于是封装了一个插件类,方便以后扩展:
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
public static class SqlMapperPlugin {
private final Predicate<Object> predicate;
private final ParamValue paramValue;

private SqlMapperPlugin(Predicate<Object> predicate,
ParamValue paramValue) {
this.predicate = predicate;
this.paramValue = paramValue;
}

//定义枚举插件
static SqlMapperPlugin EnumSqlPlugin = of(Enum.class).paramValue((value, sql) ->
Collections.singletonMap(sql, getEnumValue(value))
);
//定义Boolean插件
static SqlMapperPlugin BooleanSqlPlugin = of(Boolean.class).paramValue((value, sql) ->
Collections.singletonMap(sql, ((boolean) value) ? 1 : 0)
);

public static SqlMapperPlugin.MapperPluginsBuilder of(Predicate<Object> predicate) {
return new MapperPluginsBuilder(predicate);
}

public static SqlMapperPlugin.MapperPluginsBuilder of(Class clazz) {
return of((pd) -> {
return clazz.isAssignableFrom(pd.getClass());
});
}

boolean test(Object pd) {
return this.predicate.test(pd);
}

Map<String, Object> getParams(String sql, Object value) {
return paramValue.getParams(value, sql);
}

public static class MapperPluginsBuilder {

Predicate<Object> predicate;

public MapperPluginsBuilder(Predicate<Object> predicate) {
this.predicate = predicate;
}

public SqlMapperPlugin paramValue(SqlMapperPlugin.ParamValue paramValue) {
return new SqlMapperPlugin(this.predicate, paramValue);
}
}

@FunctionalInterface
public interface ParamValue {
Map<String, Object> getParams(Object var1, String var2);
}
}

public static String getEnumValue(Object value) {
Method method;
try {
method = value.getClass()
.getMethod("name");
return String.valueOf(method.invoke(value));
} catch (NoSuchMethodException e) {
throw new RuntimeException("NoSuchMethodException", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("IllegalAccessException", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("InvocationTargetException", e);
}
}

主要用到了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
5
public 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
7
SqlWhereBuffer.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函数式风格的效果。