CrazyAirhead

疯狂的傻瓜,傻瓜也疯狂——傻方能执著,疯狂才专注!

0%

加餐 —— 扩展 easy-query

说明

在「 操作 SQL 」中说到,个人比较喜欢 ActiveRecord,后续也会补充对 easy-query 的扩展,比如集成 ActiveRecord 的 SQL 模版管理,基于 Map 的 Model Bean。

可能自己最早是开始从 SQL 开始也业务的,不是一开始就接触 ORM,所以个人习惯使用 SQL 进行代码的编写,特别是有连接的时候,虽然 easy-query 对 join 处理已经很好了,但我还是觉得书写比较麻烦。好在 easy-query提供了原生的 SQL 查询的支持,因此只需要增加 SQL 模版的管理功能,就能使用安心的使用 SQL 写查询了。

在使用 Mybatis-Plus 的过程中发现,这个 ORM 框架在多租户是最无感的。因为引入了 jsqlparser,不论是普通的 orm 的 dsl 写法,还是 SQL 写法都不需要关心租户字段。其他的一些 ORM 框架只是关注 dsl 的写法是能租户字段无感的,在写 SQL 时就需要自己处理租户字段,easy-query 也不例外,jfinal 也是如此。

基于 Map 的 Model Bean,或者 ActiveRecord 的一个好处就是对查询结果的动态扩展或者裁剪,当然这都是要配合 Jfinal 自己的 JSON 序列化,否则动态增加的字段是不会被处理的,另外就是 Jfinal 还提供了 keep 和 remove 等方法可以对数据进行裁剪。这样的好处是什么呢?在代码中可以统一使用一套 Modle 进行传递,而不是各种 Vo 和 Dto 的转换。

自己使用 easy-query 的也不是很熟练,所以这次的调整不一定很完整,这里相当于是提供了一个思路供大家。阅读本文的同学根据自己的情况谨慎使用和自行修改。因为本次的代码的集成修改在另一个项目中(依赖有其他项目的依赖,增加了easyquery和easy-query-web 插件),还没有整理到 porpoise-demo 项目中,所以暂时看不了源码。后续剥离项目的依赖后会陆续上传到 porpoise-demo 中。

Enjoy

因为 AtiveRecord 使用了 enjoy ,做了模版管理的基础,因此可以使用引入依赖包的方式引入,然而原生的 enjoy 对 solon 的容器支持问题(https://gitee.com/jfinal/jfinal/pulls/119),solon 对 enjoy 进行了重新编译。在使用的过程中,发现其实单独版本的 enjoy 和 activerecord 还是有些裁剪的。因此自己整理重新 fork 了一个enjoy 和 activerecord 的版本,基于 5.2.2 版本做了些修改,代码托管在 coding。

地址:

https://e.coding.net/goldsyear/allblue/porpoise-jfinal.git

坐标:

implementation(“com.goldsyear:enjoy:1.0.0”)

ActiveRecord

增加 acitverecord 代码

首先 easy-query 提供了 solon 的插件,将 easy-query 的 solon 插件克隆出来。jfinal 的 ActiveRecord 主要 plugin/activerecord 目录。因此先把两个目录整合在一起,修复其中的编译错误,一些缺失不会处理的可以先屏蔽,确保已经被编辑过来。

img

初始化 SqlKit

Jfinal 通过 SqlKit 管理 SQL 模版的加载。因此在构建的 easy-qeury 的 bean 对象的时候,一起初始化 SqlKit,加载对应的SQL 模版。

EasyQueryHolder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.easy.query.solon.integration.holder;

import com.easy.query.core.api.client.EasyQueryClient;
import com.easy.query.solon.activerecord.sql.SqlKit;
import org.noear.solon.core.VarHolder;

/**
* create time 2023/7/24 22:19 文件说明
*
* @author xuejiaming
*/
public interface EasyQueryHolder {
String MAIN_CONFIG_NAME = "main";

/**
* 获取sqlKet
*
* @return
*/
SqlKit sqlKit();

// 以下代码省略,方便说明修改 ...
}

DbManger

在构建增加 SqlKit 的初始化,这个通过读取配置,获取 SQL 的文件位置并读取.sql的文件进行装在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** 构建 */
private static EasyQueryHolder build(BeanWrap bw) {
// 以上代码省略,方便说明修改 ...

// 引入对应的sql模版
SqlKit sqlKit = new SqlKit(configName, solonEasyQueryProperties.getSqlDev());
String sqlPath = solonEasyQueryProperties.getSqlPath();
ScanUtil.scan(sqlPath, n -> n.endsWith(".sql")).forEach(sqlKit::addSqlTemplate);
sqlKit.parseSqlTemplate();
SqlManager.add(sqlKit);
EventBus.publish(sqlKit);

return injectHolderFactory.get().getHolder(configName, easyQueryClient, sqlKit);
}

使用

通过SqlManger(对应DbManger),获取SQL 语法,直接交给 entityQueryable 进行原生的 SQL 查询,而且可以进行类型转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 通过sql模板,查询数据
*
* @param template
* @param data
* @param vClass
* @return
* @param <V>
*/
public <V> List<V> listAs(String template, Map<?, ?> data, Class<V> vClass) {
SqlPara sqlPara = SqlManager.getSqlPara(template, data);
return entityQueryable(sqlPara.getSql(), Arrays.asList(sqlPara.getPara()))
.select(vClass)
.toList();
}

SQL 租户

Easy-query 的 DefaultEntityExpressionExecutor 支持在 querySQL 中对原生的SQL 进行处理,可以在这里增加对租户的逻辑的处理。

依赖

1
api("com.github.jsqlparser:jsqlparser:5.1")

DefaultEntityExpressionExecutorEx

增加租户逻辑的处理,如果存在 TenantLineHandler 的bean 对象,且没有设置忽略租户的时候就进行解析SQL语法,并进行租户的过滤。

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
package com.easy.query.solon.integration.excutor;

import com.easy.query.core.basic.jdbc.executor.DefaultEntityExpressionExecutor;
import com.easy.query.core.basic.jdbc.executor.ExecutorContext;
import com.easy.query.core.basic.jdbc.executor.ResultMetadata;
import com.easy.query.core.basic.jdbc.parameter.SQLParameter;
import com.easy.query.core.expression.executor.parser.EasyPrepareParser;
import com.easy.query.core.expression.executor.query.ExecutionContextFactory;
import com.easy.query.core.inject.ServiceProvider;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.core.AppContext;

/**
* @author airhead
*/
@Slf4j
public class DefaultEntityExpressionExecutorEx extends DefaultEntityExpressionExecutor {
private final ServiceProvider serviceProvider;

public DefaultEntityExpressionExecutorEx(
EasyPrepareParser easyPrepareParser,
ExecutionContextFactory executionContextFactory,
ServiceProvider serviceProvider) {
super(easyPrepareParser, executionContextFactory);
this.serviceProvider = serviceProvider;
}

@Override
public <TR> List<TR> querySQL(
ExecutorContext executorContext,
ResultMetadata<TR> resultMetadata,
String sql,
List<SQLParameter> sqlParameters) {
AppContext appContext = serviceProvider.getService(AppContext.class);
TenantLineHandler tenantLineHandler = appContext.getBean(TenantLineHandler.class);
if (tenantLineHandler == null || tenantLineHandler.ignoreTenant()) {
return super.querySQL(executorContext, resultMetadata, sql, sqlParameters);
}

sql = tenantLineHandler.parseSql(sql, null);
return super.querySQL(executorContext, resultMetadata, sql, sqlParameters);
}
}

TenantLineHandler

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
package com.easy.query.solon.integration.excutor;

import com.easy.query.solon.integration.parser.SqlParser;
import com.easy.query.solon.integration.parser.TenantLineSqlParser;
import net.sf.jsqlparser.expression.*;

/**
* @author airhead
*/
public interface TenantLineHandler {

/**
* 获取租户id参数
*
* @return
*/
Expression getTenantId();

/**
* 获取租户id字段
*
* @return
*/
default String getTenantIdColumn() {
return "tenant_id";
}

/**
* 是否忽略租户
*
* @return
*/
default boolean ignoreTenant() {
return false;
}

/**
* 是否忽略表格
*
* @param tableName
* @return
*/
default boolean ignoreTable(String tableName) {
return false;
}

/**
* 获取SQL 解析器
*
* @return
*/
default SqlParser getSqlParser() {
return new TenantLineSqlParser(this);
}

/**
* 解析SQL
*
* @param sql
* @param obj
* @return
*/
default String parseSql(String sql, Object obj) {
return getSqlParser().parserSingle(sql, obj);
}
}

SqlParser

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
package com.easy.query.solon.integration.parser;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.Statements;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;

/**
* @author airhead
*/
public interface SqlParser {

/**
* 解析单条 SQL
*
* @param sql
* @param obj
* @return
*/
default String parserSingle(String sql, Object obj) {
try {
Statement statement = CCJSqlParserUtil.parse(sql);
return this.processParser(statement, 0, sql, obj);
} catch (JSQLParserException e) {
throw new RuntimeException(
String.format("Failed to process, Error SQL: %s", sql), e.getCause());
}
}

/**
* 解析多条 SQL
*
* @param sql
* @param obj
* @return
*/
@SuppressWarnings("deprecation")
default String parserMulti(String sql, Object obj) {
try {
StringBuilder sb = new StringBuilder();
Statements statements = CCJSqlParserUtil.parseStatements(sql);
int i = 0;

for (Statement statement : statements.getStatements()) {
if (i > 0) {
sb.append(";");
}

sb.append(this.processParser(statement, i, sql, obj));
++i;
}

return sb.toString();
} catch (JSQLParserException e) {
throw new RuntimeException(
String.format("Failed to process, Error SQL: %s", sql), e.getCause());
}
}

default String processParser(Statement statement, int index, String sql, Object obj) {
if (statement instanceof Insert) {
this.processInsert((Insert) statement, index, sql, obj);
} else if (statement instanceof Select) {
this.processSelect((Select) statement, index, sql, obj);
} else if (statement instanceof Update) {
this.processUpdate((Update) statement, index, sql, obj);
} else if (statement instanceof Delete) {
this.processDelete((Delete) statement, index, sql, obj);
}

sql = statement.toString();

return sql;
}

default void processInsert(Insert insert, int index, String sql, Object obj) {
throw new UnsupportedOperationException();
}

default void processDelete(Delete delete, int index, String sql, Object obj) {
throw new UnsupportedOperationException();
}

default void processUpdate(Update update, int index, String sql, Object obj) {
throw new UnsupportedOperationException();
}

default void processSelect(Select select, int index, String sql, Object obj) {
throw new UnsupportedOperationException();
}
}

TenantLineSqlParser

重点处理 select,如果是插入同时是对单表的操作,使用 ORM 的 dsl 进行操作就可以了。

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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
package com.easy.query.solon.integration.parser;

import com.easy.query.solon.integration.excutor.TenantLineHandler;
import java.util.*;
import java.util.stream.Collectors;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.*;
import org.dromara.hutool.core.collection.CollUtil;

/**
* @author airhead
*/
public class TenantLineSqlParser implements SqlParser {
private final TenantLineHandler tenantLineHandler;

public TenantLineSqlParser(TenantLineHandler tenantLineHandler) {
this.tenantLineHandler = tenantLineHandler;
}

@Override
public void processSelect(Select select, int index, String sql, Object obj) {
String whereSegment = (String) obj;
this.processSelectBody(select.getPlainSelect(), whereSegment);
List<WithItem<?>> withItemsList = select.getWithItemsList();
if (CollUtil.isNotEmpty(withItemsList)) {
withItemsList.forEach(
(withItem) -> this.processSelectBody(withItem.getSelect(), whereSegment));
}
}

protected void processSelectBody(Select selectBody, final String whereSegment) {
if (selectBody == null) {
return;
}

if (selectBody instanceof PlainSelect) {
processPlainSelect((PlainSelect) selectBody, whereSegment);
} else if (selectBody instanceof ParenthesedSelect parenthesedSelect) {
processSelectBody(parenthesedSelect.getSelect(), whereSegment);
} else if (selectBody instanceof SetOperationList operationList) {
List<Select> selectBodyList = operationList.getSelects();
if (CollUtil.isNotEmpty(selectBodyList)) {
selectBodyList.forEach(body -> processSelectBody(body, whereSegment));
}
}
}

protected void processPlainSelect(final PlainSelect plainSelect, final String whereSegment) {
List<SelectItem<?>> selectItems = plainSelect.getSelectItems();
if (CollUtil.isNotEmpty(selectItems)) {
selectItems.forEach((selectItem) -> this.processSelectItem(selectItem, whereSegment));
}

Expression where = plainSelect.getWhere();
this.processWhereSubSelect(where, whereSegment);
FromItem fromItem = plainSelect.getFromItem();
List<Table> list = this.processFromItem(fromItem, whereSegment);
List<Table> mainTables = new ArrayList<>(list);
List<Join> joins = plainSelect.getJoins();
if (CollUtil.isNotEmpty(joins)) {
mainTables = this.processJoins(mainTables, joins, whereSegment);
}

if (CollUtil.isNotEmpty(mainTables)) {
plainSelect.setWhere(this.builderExpression(where, mainTables, whereSegment));
}
}

@SuppressWarnings({"rawtypes", "unchecked"})
protected void processWhereSubSelect(Expression where, final String whereSegment) {
if (where == null) {
return;
}
if (where instanceof FromItem) {
processOtherFromItem((FromItem) where, whereSegment);
return;
}
if (where.toString().indexOf("SELECT") > 0) {
// 有子查询
if (where instanceof BinaryExpression expression) {
// 比较符号 , and , or , 等等
processWhereSubSelect(expression.getLeftExpression(), whereSegment);
processWhereSubSelect(expression.getRightExpression(), whereSegment);
} else if (where instanceof InExpression expression) {
// in
Expression inExpression = expression.getRightExpression();
if (inExpression instanceof Select) {
processSelectBody(((Select) inExpression), whereSegment);
}
} else if (where instanceof ExistsExpression expression) {
// exists
processWhereSubSelect(expression.getRightExpression(), whereSegment);
} else if (where instanceof NotExpression expression) {
// not exists
processWhereSubSelect(expression.getExpression(), whereSegment);
} else if (where instanceof ParenthesedExpressionList) {
ParenthesedExpressionList<Expression> expression = (ParenthesedExpressionList) where;
processWhereSubSelect(expression.get(0), whereSegment);
}
}
}

@SuppressWarnings("rawtypes")
protected void processSelectItem(SelectItem selectItem, final String whereSegment) {
Expression expression = selectItem.getExpression();
if (expression instanceof Select) {
processSelectBody(((Select) expression), whereSegment);
} else if (expression instanceof Function) {
processFunction((Function) expression, whereSegment);
} else if (expression instanceof ExistsExpression existsExpression) {
processSelectBody((Select) existsExpression.getRightExpression(), whereSegment);
}
}

/**
* 处理函数
*
* <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)
*
* <p>
*
* <p>fixed gitee pulls/141
*
* @param function
*/
protected void processFunction(Function function, final String whereSegment) {
ExpressionList<?> parameters = function.getParameters();
if (parameters != null) {
parameters.forEach(
expression -> {
if (expression instanceof Select) {
processSelectBody(((Select) expression), whereSegment);
} else if (expression instanceof Function) {
processFunction((Function) expression, whereSegment);
} else if (expression instanceof EqualsTo) {
if (((EqualsTo) expression).getLeftExpression() instanceof Select) {
processSelectBody(
((Select) ((EqualsTo) expression).getLeftExpression()), whereSegment);
}
if (((EqualsTo) expression).getRightExpression() instanceof Select) {
processSelectBody(
((Select) ((EqualsTo) expression).getRightExpression()), whereSegment);
}
}
});
}
}

protected void processOtherFromItem(FromItem fromItem, final String whereSegment) {
// 去除括号
while (fromItem instanceof ParenthesedFromItem) {
fromItem = ((ParenthesedFromItem) fromItem).getFromItem();
}

if (fromItem instanceof ParenthesedSelect) {
Select subSelect = (Select) fromItem;
processSelectBody(subSelect, whereSegment);
}
}

/** 处理条件 */
protected Expression builderExpression(
Expression currentExpression, List<Table> tables, final String whereSegment) {
// 没有表需要处理直接返回
if (CollUtil.isEmpty(tables)) {
return currentExpression;
}
// 构造每张表的条件
List<Expression> expressions =
tables.stream()
.map(item -> buildTableExpression(item, currentExpression, whereSegment))
.filter(Objects::nonNull)
.collect(Collectors.toList());

// 没有表需要处理直接返回
if (CollUtil.isEmpty(expressions)) {
return currentExpression;
}

// 注入的表达式
Expression injectExpression = expressions.get(0);
// 如果有多表,则用 and 连接
if (expressions.size() > 1) {
for (int i = 1; i < expressions.size(); i++) {
injectExpression = new AndExpression(injectExpression, expressions.get(i));
}
}

if (currentExpression == null) {
return injectExpression;
}
if (currentExpression instanceof OrExpression) {
return new AndExpression(
new ParenthesedExpressionList<>(currentExpression), injectExpression);
} else {
return new AndExpression(currentExpression, injectExpression);
}
}

/**
* 租户字段别名设置
*
* <p>tenantId 或 tableAlias.tenantId
*
* @param table 表对象
* @return 字段
*/
protected Column getAliasColumn(Table table) {
StringBuilder column = new StringBuilder();
// todo 该起别名就要起别名,禁止修改此处逻辑
if (table.getAlias() != null) {
column.append(table.getAlias().getName()).append(".");
}
column.append(tenantLineHandler.getTenantIdColumn());
return new Column(column.toString());
}

/**
* 构建租户条件表达式
*
* @param table 表对象
* @param where 当前where条件
* @param whereSegment 所属Mapper对象全路径(在原租户拦截器功能中,这个参数并不需要参与相关判断)
* @return 租户条件表达式
*/
protected Expression buildTableExpression(
final Table table, final Expression where, final String whereSegment) {
if (tenantLineHandler.ignoreTable(table.getName())) {
return null;
}

return new EqualsTo(getAliasColumn(table), tenantLineHandler.getTenantId());
}

protected List<Table> processFromItem(FromItem fromItem, final String whereSegment) {
// 处理括号括起来的表达式
// while (fromItem instanceof ParenthesedFromItem) {
// fromItem = ((ParenthesedFromItem) fromItem).getFromItem();
// }

List<Table> mainTables = new ArrayList<>();
// 无 join 时的处理逻辑
if (fromItem instanceof Table) {
Table fromTable = (Table) fromItem;
mainTables.add(fromTable);
} else if (fromItem instanceof ParenthesedFromItem) {
// SubJoin 类型则还需要添加上 where 条件
List<Table> tables = processSubJoin((ParenthesedFromItem) fromItem, whereSegment);
mainTables.addAll(tables);
} else {
// 处理下 fromItem
processOtherFromItem(fromItem, whereSegment);
}
return mainTables;
}

/**
* 处理 sub join
*
* @param subJoin subJoin
* @return Table subJoin 中的主表
*/
protected List<Table> processSubJoin(ParenthesedFromItem subJoin, final String whereSegment) {
while (subJoin.getJoins() == null && subJoin.getFromItem() instanceof ParenthesedFromItem) {
subJoin = (ParenthesedFromItem) subJoin.getFromItem();
}
List<Table> tableList = processFromItem(subJoin.getFromItem(), whereSegment);
List<Table> mainTables = new ArrayList<>(tableList);
if (subJoin.getJoins() != null) {
processJoins(mainTables, subJoin.getJoins(), whereSegment);
}
return mainTables;
}

/**
* 处理 joins
*
* @param mainTables 可以为 null
* @param joins join 集合
* @return List<Table> 右连接查询的 Table 列表
*/
protected List<Table> processJoins(
List<Table> mainTables, List<Join> joins, final String whereSegment) {
// join 表达式中最终的主表
Table mainTable = null;
// 当前 join 的左表
Table leftTable = null;

if (mainTables.size() == 1) {
mainTable = mainTables.get(0);
leftTable = mainTable;
}

// 对于 on 表达式写在最后的 join,需要记录下前面多个 on 的表名
Deque<List<Table>> onTableDeque = new LinkedList<>();
for (Join join : joins) {
// 处理 on 表达式
FromItem joinItem = join.getRightItem();

// 获取当前 join 的表,subJoint 可以看作是一张表
List<Table> joinTables = null;
if (joinItem instanceof Table) {
joinTables = new ArrayList<>();
joinTables.add((Table) joinItem);
} else if (joinItem instanceof ParenthesedFromItem) {
joinTables = processSubJoin((ParenthesedFromItem) joinItem, whereSegment);
}

if (joinTables != null && !joinTables.isEmpty()) {

// 如果是隐式内连接
if (join.isSimple()) {
mainTables.addAll(joinTables);
continue;
}

// 当前表是否忽略
Table joinTable = joinTables.get(0);

List<Table> onTables = null;
// 如果不要忽略,且是右连接,则记录下当前表
if (join.isRight()) {
mainTable = joinTable;
mainTables.clear();
if (leftTable != null) {
onTables = Collections.singletonList(leftTable);
}
} else if (join.isInner()) {
if (mainTable == null) {
onTables = Collections.singletonList(joinTable);
} else {
onTables = Arrays.asList(mainTable, joinTable);
}
mainTable = null;
mainTables.clear();
} else {
onTables = Collections.singletonList(joinTable);
}

if (mainTable != null && !mainTables.contains(mainTable)) {
mainTables.add(mainTable);
}

// 获取 join 尾缀的 on 表达式列表
Collection<Expression> originOnExpressions = join.getOnExpressions();
// 正常 join on 表达式只有一个,立刻处理
if (originOnExpressions.size() == 1 && onTables != null) {
List<Expression> onExpressions = new LinkedList<>();
onExpressions.add(
builderExpression(originOnExpressions.iterator().next(), onTables, whereSegment));
join.setOnExpressions(onExpressions);
leftTable = mainTable == null ? joinTable : mainTable;
continue;
}
// 表名压栈,忽略的表压入 null,以便后续不处理
onTableDeque.push(onTables);
// 尾缀多个 on 表达式的时候统一处理
if (originOnExpressions.size() > 1) {
Collection<Expression> onExpressions = new LinkedList<>();
for (Expression originOnExpression : originOnExpressions) {
List<Table> currentTableList = onTableDeque.poll();
if (CollUtil.isEmpty(currentTableList)) {
onExpressions.add(originOnExpression);
} else {
onExpressions.add(
builderExpression(originOnExpression, currentTableList, whereSegment));
}
}
join.setOnExpressions(onExpressions);
}
leftTable = joinTable;
} else {
processOtherFromItem(joinItem, whereSegment);
leftTable = null;
}
}

return mainTables;
}
}

DbManager

在DbManager 中替换默认的 EntityExpressionExecutor。

1
2
3
4
5
easyQueryBuilderConfiguration
.replaceService(DataSourceUnitFactory.class, SolonDataSourceUnitFactory.class)
.replaceService(ConnectionManager.class, SolonConnectionManager.class)
.replaceService(appContext)
.replaceService(EntityExpressionExecutor.class, DefaultEntityExpressionExecutorEx.class);

使用租户

使用时只需要继承 TenantLineHandler,设置对应的租户字段的处理,构建为Bean 对象就可以了。

结构裁剪

虽然 solon 可以设置数据为 null 时不返回属性,但这个和返回结果的裁剪还是有不同。如果通过 easy-query 来实现的话,就需要增加对的 Vo,或者 Dto 来承接数据。这里提供对 Model 进行封装的方式实现对返回结果结构的裁剪。

BaseModel

通过一个baseModel 看和普通model的不同。

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
package com.goldsyear.porpoise.module.dev.code.model.base;

import com.easy.query.core.annotation.Column;
import com.easy.query.solon.activerecord.IBean;
import com.easy.query.solon.activerecord.Model;
import java.time.LocalDateTime;

/**
* 1. private 变量在 Getter 和 Setter 中没有被用到。之所以定义它们,是因为很多序列化框架的需要 private 变量和 Setter 必须同时存在才能成功。 2.
* 方便设置 easy-query 的注解
*
* <p>Generated by Porpoise, please do not modify this file.
*
* @author airhead
*/
@SuppressWarnings({"unused"})
public abstract class BaseCodeDb<M extends BaseCodeDb<M>> extends Model<M> implements IBean {

/** 主键编号 */
@Column(primaryKey = true, generatedKey = true)
private Integer id;

/** 名称 */
private String name;

/** 连接 */
private String url;

/** 用户名 */
private String username;

/** 密码 */
private String password;

/** 创建者 */
private String creator;

/** 创建时间 */
private LocalDateTime createTime;

/** 更新者 */
private String updater;

/** 更新时间 */
private LocalDateTime updateTime;

/** 主键编号 */
public Integer getId() {
return getInt("id");
}

/** 主键编号 */
public void setId(Integer id) {
set("id", id);
}

/** 名称 */
public String getName() {
return getStr("name");
}

/** 名称 */
public void setName(String name) {
set("name", name);
}

/** 连接 */
public String getUrl() {
return getStr("url");
}

/** 连接 */
public void setUrl(String url) {
set("url", url);
}

/** 用户名 */
public String getUsername() {
return getStr("username");
}

/** 用户名 */
public void setUsername(String username) {
set("username", username);
}

/** 密码 */
public String getPassword() {
return getStr("password");
}

/** 密码 */
public void setPassword(String password) {
set("password", password);
}

/** 创建者 */
public String getCreator() {
return getStr("creator");
}

/** 创建者 */
public void setCreator(String creator) {
set("creator", creator);
}

/** 创建时间 */
public LocalDateTime getCreateTime() {
return getLocalDateTime("create_time");
}

/** 创建时间 */
public void setCreateTime(LocalDateTime createTime) {
set("create_time", createTime);
}

/** 更新者 */
public String getUpdater() {
return getStr("updater");
}

/** 更新者 */
public void setUpdater(String updater) {
set("updater", updater);
}

/** 更新时间 */
public LocalDateTime getUpdateTime() {
return getLocalDateTime("update_time");
}

/** 更新时间 */
public void setUpdateTime(LocalDateTime updateTime) {
set("update_time", updateTime);
}
}

ModelUtils

通过 ModelUtils,可以对查询结果进行裁剪。

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
package com.goldsyear.solon.web.util;

import com.easy.query.solon.activerecord.Model;
import com.goldsyear.solon.web.model.PageResult;
import java.util.List;
import org.dromara.hutool.core.collection.CollUtil;

/**
* @author airhead
*/
public class ModelUtils {
public static <M extends Model<M>> M remove(M model, String attr) {
if (model == null) {
return null;
}

model.remove(attr);
return model;
}

public static <M extends Model<M>> List<M> remove(List<M> modelList, String attr) {
if (CollUtil.isEmpty(modelList)) {
return modelList;
}

for (Model<M> model : modelList) {
model.remove(attr);
}

return modelList;
}

public static <M extends Model<M>> PageResult<M> remove(PageResult<M> pageResult, String attr) {
if (pageResult == null) {
return pageResult;
}

remove(pageResult.getRecords(), attr);
return pageResult;
}

public static <M extends Model<M>> M remove(M model, String... attrs) {
if (model == null) {
return model;
}

model.remove(attrs);
return model;
}

public static <M extends Model<M>> List<M> remove(List<M> modelList, String... attrs) {
if (CollUtil.isEmpty(modelList)) {
return modelList;
}

for (Model<M> model : modelList) {
model.remove(attrs);
}

return modelList;
}

public static <M extends Model<M>> PageResult<M> remove(
PageResult<M> pageResult, String... attrs) {
if (pageResult == null) {
return pageResult;
}

remove(pageResult.getRecords(), attrs);

return pageResult;
}

public static <M extends Model<M>> M keep(M model, String attr) {
if (model == null) {
return model;
}

model.keep(attr);
return model;
}

public static <M extends Model<M>> List<M> keep(List<M> modelList, String attr) {
if (CollUtil.isEmpty(modelList)) {
return modelList;
}

for (Model<M> model : modelList) {
model.keep(attr);
}

return modelList;
}

public static <M extends Model<M>> PageResult<M> keep(PageResult<M> pageResult, String attr) {
if (pageResult == null) {
return pageResult;
}

keep(pageResult.getRecords(), attr);

return pageResult;
}

public static <M extends Model<M>> M keep(M model, String... attrs) {
if (model == null) {
return model;
}

model.keep(attrs);

return model;
}

public static <M extends Model<M>> List<M> keep(List<M> modelList, String... attrs) {
if (CollUtil.isEmpty(modelList)) {
return modelList;
}

for (Model<M> model : modelList) {
model.keep(attrs);
}

return modelList;
}

public static <M extends Model<M>> PageResult<M> keep(PageResult<M> pageResult, String... attrs) {
if (pageResult == null) {
return pageResult;
}

keep(pageResult.getRecords(), attrs);

return pageResult;
}
}

AOP

Deform

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
package com.goldsyear.solon.web.annotation;

import java.lang.annotation.*;

/**
* @author airhead
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Deform {
/**
* 保留的属性
*
* @return String[]
*/
String[] keep() default {};

/**
* 移除的属性
*
* @return String[]
*/
String[] remove() default {};
}

DeformInterceptor

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
package com.goldsyear.solon.web.interceptor;

import com.easy.query.solon.activerecord.Model;
import com.goldsyear.solon.web.annotation.Deform;
import com.goldsyear.solon.web.model.PageResult;
import com.goldsyear.solon.web.util.ModelUtils;
import java.util.Arrays;
import java.util.List;
import org.dromara.hutool.core.collection.CollUtil;
import org.noear.solon.core.aspect.Interceptor;
import org.noear.solon.core.aspect.Invocation;

/**
* @author airhead
*/
public class DeformInterceptor implements Interceptor {
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public Object doIntercept(Invocation inv) throws Throwable {
Object ret = inv.invoke();

Deform deform = inv.getMethodAnnotation(Deform.class);
// 不需要转换
if (deform == null) {
return ret;
}

if (ret instanceof Model) {
if (CollUtil.isNotEmpty(Arrays.asList(deform.keep()))) {
Model model = (Model) ret;
return ModelUtils.keep(model, deform.keep());
}

if (CollUtil.isNotEmpty(Arrays.asList(deform.remove()))) {
Model model = (Model) ret;
return ModelUtils.keep(model, deform.keep());
}
}

if (ret instanceof PageResult) {
if (CollUtil.isNotEmpty(Arrays.asList(deform.keep()))) {
PageResult pageResult = (PageResult) ret;
return ModelUtils.keep(pageResult.getRecords(), deform.keep());
}

if (CollUtil.isNotEmpty(Arrays.asList(deform.remove()))) {
PageResult pageResult = (PageResult) ret;
return ModelUtils.remove(pageResult.getRecords(), deform.keep());
}
}

if (ret instanceof List) {
if (CollUtil.isNotEmpty(Arrays.asList(deform.keep()))) {
List list = (List) ret;
return ModelUtils.keep(list, deform.keep());
}

if (CollUtil.isNotEmpty(Arrays.asList(deform.remove()))) {
List list = (List) ret;
return ModelUtils.remove(list, deform.keep());
}
}

return ret;
}
}

注册 DeformInterceptor

1
2
// 
context.beanInterceptorAdd(Deform.class, new DeformInterceptor());

使用

注意这里的是字段名,而不是类的属性名。

1
2
3
4
5
6
  @Deform(keep = {"id", "name"})
public List<CodeColumn> simpleListCodeColumn(CodeColumn codeColumn) {
List<CodeColumn> list = list(codeColumn);
// ModelUtils.keep(list, "id", "name");
return list;
}

序列化相关

因为 Model 和 Activerecord,渲染的时候并不是直接渲染成员,而是对应的 attr 成员下的内容,因此需要特殊处理。这里使用了 snack3, 如果使用了其他的json 框架,需要根据实际情况进行处理。

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
package com.goldsyear.solon.web;

import com.easy.query.solon.activerecord.Model;
import com.easy.query.solon.activerecord.Record;
import com.goldsyear.solon.web.annotation.Deform;
import com.goldsyear.solon.web.interceptor.DeformInterceptor;
import com.goldsyear.solon.web.model.ModelEncoder;
import com.goldsyear.solon.web.model.ModelFieldGetter;
import com.goldsyear.solon.web.model.RecordEncoder;
import com.goldsyear.solon.web.model.RecordFieldGetter;
import com.jfinal.kit.PathKit;
import com.jfinal.template.Engine;
import java.time.LocalDateTime;
import org.dromara.hutool.core.date.TimeUtil;
import org.noear.snack.core.Feature;
import org.noear.snack.core.Options;
import org.noear.solon.core.AppContext;
import org.noear.solon.core.Plugin;
import org.noear.solon.serialization.snack3.SnackActionExecutor;
import org.noear.solon.serialization.snack3.SnackRenderFactory;
import org.noear.solon.web.staticfiles.StaticMappings;
import org.noear.solon.web.staticfiles.repository.FileStaticRepository;

/**
* @author airhead
*/
public class EasyQueryWebPluginImp implements Plugin {
static boolean started = false;

@Override
public void start(AppContext context) {
if (started) {
return;
}

StaticMappings.add("/", new FileStaticRepository(PathKit.getWebRootPath()));

Engine.addFieldGetterToLast(new ModelFieldGetter());
Engine.addFieldGetterToLast(new RecordFieldGetter());

// 处理序列化
context.getBeanAsync(
SnackRenderFactory.class,
factory -> {
factory.addConvertor(LocalDateTime.class, TimeUtil::formatNormal);
factory.addEncoder(Model.class, new ModelEncoder<>());
factory.addEncoder(Record.class, new RecordEncoder<>());
});

// 处理反序列化,通过setter来设值。
context.getBeanAsync(
SnackActionExecutor.class,
snackActionExecutor -> {
Options config = snackActionExecutor.config();
config.add(Feature.UseSetter, Feature.DisableClassNameRead);
});

// 注册 DeformInterceptor
context.beanInterceptorAdd(Deform.class, new DeformInterceptor());

started = true;
}
}

Base Encoder

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
package com.goldsyear.solon.web.model;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.noear.snack.ONode;
import org.noear.solon.Solon;
import org.noear.solon.core.Props;

/**
* @author airhead
*/
public class BaseEncoder {
/** 对 Model 和 Record 的字段名进行转换的函数。例如转成驼峰形式对 oracle 支持更友好 */
protected static Function<String, String> fieldNameConverter = StrUtil::toCamelCase;

protected Props props = Solon.cfg().getProp("solon.serialization.json");

protected void encode(ONode node, Map<String, Object> map) {
if (CollUtil.isEmpty(map)) {
return;
}

Map<String, Object> newMap = new HashMap<>(map.size());
for (Map.Entry<String, Object> entry : map.entrySet()) {
String fieldName = entry.getKey();
Object value = entry.getValue();
String attrName = fieldName;
if (fieldNameConverter != null) {
attrName = fieldNameConverter.apply(fieldName);
}
newMap.put(attrName, value);
}

node.fill(newMap);
}
}

ModelEncoder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.goldsyear.solon.web.model;

import com.easy.query.solon.activerecord.CPI;
import com.easy.query.solon.activerecord.Model;
import org.noear.snack.ONode;
import org.noear.snack.core.NodeEncoder;

/**
* @author airhead
*/
public class ModelEncoder<T extends Model<?>> extends BaseEncoder implements NodeEncoder<T> {
@Override
public void encode(T data, ONode node) {
if (data == null) {
return;
}

encode(node, CPI.getAttrs(data));
}
}

RecordEncorder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.goldsyear.solon.web.model;

import com.easy.query.solon.activerecord.Record;
import org.noear.snack.ONode;
import org.noear.snack.core.NodeEncoder;

/**
* @author airhead
*/
public class RecordEncoder<T extends Record> extends BaseEncoder implements NodeEncoder<T> {

@Override
public void encode(T data, ONode node) {
if (data == null) {
return;
}

encode(node, data.getColumns());
}
}

获取 Model

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
protected <T> T getBean(Class<T> type) {
T t = this.context().paramAsBean(type);
if (Model.class.isAssignableFrom(type)) {
if (t != null) {
Model<?> model = (Model<?>) t;
for (KeyValues<String> values : context().paramMap()) {
// 此处通过Map的方式设置
model.put(NamingCase.toUnderlineCase(values.getKey()), values.getFirstValue());
}

return t;
}
}

return t;
}

protected <T> T getModel(Class<T> type) {
return getModel(type, StrKit.firstCharToLowerCase(type.getSimpleName()));
}

protected <T> T getModel(Class<T> type, String name) {
MultiMap<String> paramMap = context().paramMap();
return PropsConverter.global().convert(new Props().addAll(paramMap).getProp(name), type);
}

小结

通过以上的扩展,easy-query 已经可以使用 ActiveRecord Model 的功能,可以用 SQL 模版的方式管理 SQL,并增强了ActiveRecord 的 SQL 能力,支持租户字段的过滤。同时借助 Solon 的 AOP 的能力,通过一个注解实现对结果结构的裁剪,减少转换类。

欢迎关注我的其它发布渠道