基于 MybatisPlus 的动态 sql 构建器
- 核心思路概述 SELECT 和 JOIN 子句:仍然由 DynamicSqlBuilder 根据元数据手动构建 SQL 字符串。MyBatis-Plus 的 Wrapper 在处理动态表名和复杂 JOIN 方面不直接提供类似 where 的 fluent API。
WHERE 子句:完全利用 MyBatis-Plus 的 LambdaQueryWrapper 来构建。这将确保参数化查询和条件的链式调用能力。
SQL 执行:通过 DynamicReportMapper(使用 @SelectProvider 等)接收构建好的完整 SQL 字符串和 Wrapper 内部生成的参数 Map,并交由 MyBatis-Plus 框架执行。
classDiagram
class DynamicSqlBuilder {
+buildSelectAndWhere(metadata, filters) Tuple<String, QueryWrapper<?>>
+buildCountSql(selectAndWhereTuple) String
+buildInsertSql(metadata, data) Tuple<String, Map<String,Object>>
+buildUpdateSql(metadata, data, primaryKeyValue) Tuple<String, Map<String,Object>>
+buildDeleteSql(metadata, primaryKeyValue) Tuple<String, Map<String,Object>>
-buildWhereWrapper(metadata, filters) LambdaQueryWrapper
}
class ReportMetadata {
+getTableName()
+getPrimaryKeyField()
+getFields() List~FieldMetadata~
+getQueryConditions() List~QueryConditionMetadata~
+getRelations() List~RelationMetadata~
+getFieldByFieldName(fieldName) Optional~FieldMetadata~
+getOperationByType(type) Optional~OperationMetadata~
}
class FieldMetadata
class QueryConditionMetadata
class RelationMetadata
DynamicSqlBuilder ..> ReportMetadata
DynamicSqlBuilder ..> FieldMetadata
DynamicSqlBuilder ..> QueryConditionMetadata
DynamicSqlBuilder ..> RelationMetadata
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
// com.example.report.core.sql.DynamicSqlBuilder
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.HashMap;
@Component
@RequiredArgsConstructor
public class DynamicSqlBuilder {
// 辅助元组,用于返回 SQL 字符串和 QueryWrapper
// String 部分包含 SELECT ... FROM ... JOIN ...
// QueryWrapper 包含 WHERE 和 ORDER BY 条件
public record SqlAndWrapperTuple(String selectFromJoinSql, LambdaQueryWrapper<?> queryWrapper) {}
/**
* 构建 SELECT, FROM 和 JOIN 子句的 SQL 字符串,并创建 WHERE 条件的 LambdaQueryWrapper。
*
* @param metadata 报表元数据
* @param filters 筛选条件
* @return 包含 SQL 字符串和 LambdaQueryWrapper 的元组
*/
public SqlAndWrapperTuple buildSelectAndWhere(
ReportMetadata metadata,
Map<String, Object> filters
) {
StringBuilder selectFromJoinSql = new StringBuilder();
// 1. SELECT 子句
List<String> selectFields = metadata.getFields().stream()
.filter(FieldMetadata::getIsVisible)
.map(f -> "`" + f.getFieldName() + "`" + (f.getDisplayName() != null && !f.getDisplayName().equals(f.getFieldName()) ? (" AS `" + f.getDisplayName() + "`") : ""))
.collect(Collectors.toList());
selectFromJoinSql.append("SELECT ").append(String.join(", ", selectFields));
// 2. FROM 和 JOIN 子句
selectFromJoinSql.append(" FROM `").append(metadata.getTableName()).append("` t1");
if (metadata.getRelations() != null && !metadata.getRelations().isEmpty()) {
int tableAliasIndex = 2; // t2, t3...
for (RelationMetadata relation : metadata.getRelations()) {
String relatedTableAlias = "t" + tableAliasIndex++;
selectFromJoinSql.append(" ").append(relation.getJoinType())
.append(" `").append(relation.getRelatedTableName()).append("` ").append(relatedTableAlias)
.append(" ON t1.`").append(relation.getMainTableField()).append("` = ").append(relatedTableAlias).append(".`").append(relation.getRelatedTableField()).append("`");
if (relation.getJoinCondition() != null && !relation.getJoinCondition().isEmpty()) {
selectFromJoinSql.append(" AND ").append(relation.getJoinCondition());
}
}
}
// 3. 构建 WHERE 条件的 LambdaQueryWrapper
LambdaQueryWrapper<Object> queryWrapper = buildWhereWrapper(metadata, filters);
return new SqlAndWrapperTuple(selectFromJoinSql.toString(), queryWrapper);
}
/**
* 构建 WHERE 条件的 LambdaQueryWrapper。
*
* @param metadata 报表元数据
* @param filters 筛选条件
* @return 构建好的 LambdaQueryWrapper
*/
private LambdaQueryWrapper<Object> buildWhereWrapper(
ReportMetadata metadata,
Map<String, Object> filters
) {
LambdaQueryWrapper<Object> wrapper = Wrappers.lambdaQuery();
if (filters != null && !filters.isEmpty()) {
for (Map.Entry<String, Object> entry : filters.entrySet()) {
String fieldName = entry.getKey();
Object value = entry.getValue();
FieldMetadata fieldMeta = metadata.getFieldByFieldName(fieldName)
.orElseThrow(() -> new InvalidFieldException("无效的筛选字段: " + fieldName));
if (!fieldMeta.getIsFilterable()) {
throw new FieldNotFilterableException("字段不可筛选: " + fieldName);
}
// 获取查询条件特定操作符(如果可用),否则使用默认值
String operator = metadata.getQueryConditions().stream()
.filter(qc -> qc.getFieldName().equalsIgnoreCase(fieldName))
.map(QueryConditionMetadata::getOperator)
.findFirst()
.orElse("EQ"); // 默认为等于
// 使用 fieldMeta.getFieldName() 作为数据库列名
// 注意:这里需要确保 Lambda 表达式能识别 Map 内部的键,MyBatis-Plus 的LambdaQueryWrapper
// 通常用于实体类,直接传入字符串列名在某些情况下更直接
// 对于 Map 类型,通常使用 QueryWrapper.eq("column_name", value) 这种形式
switch (operator.toUpperCase()) {
case "EQ":
wrapper.eq(fieldMeta.getFieldName(), value);
break;
case "LIKE":
wrapper.like(fieldMeta.getFieldName(), value);
break;
case "GT":
wrapper.gt(fieldMeta.getFieldName(), value);
break;
case "LT":
wrapper.lt(fieldMeta.getFieldName(), value);
break;
case "GTE":
wrapper.ge(fieldMeta.getFieldName(), value);
break;
case "LTE":
wrapper.le(fieldMeta.getFieldName(), value);
break;
case "BETWEEN": // 假设 value 是包含两个元素的列表 [start, end]
if (!(value instanceof List) || ((List<?>) value).size() != 2) {
throw new IllegalArgumentException("BETWEEN 运算符需要两个值的列表。");
}
wrapper.between(fieldMeta.getFieldName(), ((List<?>) value).get(0), ((List<?>) value).get(1));
break;
case "IN": // 假设 value 是一个列表
if (!(value instanceof List) || ((List<?>) value).isEmpty()) {
throw new IllegalArgumentException("IN 运算符需要一个非空值的列表。");
}
wrapper.in(fieldMeta.getFieldName(), (List<?>) value);
break;
default:
throw new UnsupportedOperationException("不支持的操作符: " + operator);
}
}
}
return wrapper;
}
/**
* 构建 COUNT SQL。
*
* @param selectAndWhereTuple 包含 SELECT/FROM/JOIN SQL 和 QueryWrapper 的元组
* @return COUNT SQL 字符串
*/
public String buildCountSql(SqlAndWrapperTuple selectAndWhereTuple) {
String fromAndJoin = selectAndWhereTuple.selectFromJoinSql()
.substring(selectAndWhereTuple.selectFromJoinSql().indexOf(" FROM "));
// 注意:Wrapper 的 SQL 片段会包含 WHERE,但这里我们只构建 COUNT(*) FROM 部分
// 实际执行时,Wrapper 会与 COUNT(*) 结合
return "SELECT COUNT(*) " + fromAndJoin;
}
/**
* 构建 INSERT SQL。
*
* @param metadata 报表元数据
* @param data 要插入的数据
* @return 包含 SQL 字符串和参数 Map 的元组
*/
public SqlTuple buildInsertSql(ReportMetadata metadata, Map<String, Object> data) {
List<String> columns = new ArrayList<>();
List<String> values = new ArrayList<>();
Map<String, Object> params = new HashMap<>();
int paramCount = 0;
for (FieldMetadata field : metadata.getFields()) {
if (field.getIsEditable()) {
String fieldName = field.getFieldName();
String paramKey = "param" + (paramCount++); // 使用命名参数键
if (data.containsKey(fieldName)) {
columns.add("`" + fieldName + "`");
values.add("#{" + paramKey + "}");
params.put(paramKey, data.get(fieldName));
} else if (field.getIsRequired() && field.getOptions() == null) {
throw new IllegalArgumentException("插入操作缺少必填字段 '" + field.getDisplayName() + "' (" + field.getFieldName() + ")。");
}
}
}
if (columns.isEmpty()) {
throw new IllegalArgumentException("未提供任何可编辑字段用于插入。");
}
String sql = "INSERT INTO `" + metadata.getTableName() + "` (" + String.join(", ", columns) + ") VALUES (" + String.join(", ", values) + ")";
return new SqlTuple(sql, params);
}
/**
* 构建 UPDATE SQL。
*
* @param metadata 报表元数据
* @param data 要更新的数据
* @param primaryKeyValue 主键值
* @return 包含 SQL 字符串和参数 Map 的元组
*/
public SqlTuple buildUpdateSql(ReportMetadata metadata, Map<String, Object> data, Object primaryKeyValue) {
List<String> sets = new ArrayList<>();
Map<String, Object> params = new HashMap<>();
int paramCount = 0;
for (FieldMetadata field : metadata.getFields()) {
if (field.getIsEditable()) {
String fieldName = field.getFieldName();
String paramKey = "param" + (paramCount++);
if (data.containsKey(fieldName)) {
sets.add("`" + fieldName + "` = #{" + paramKey + "}");
params.put(paramKey, data.get(fieldName));
}
}
}
if (sets.isEmpty()) {
throw new IllegalArgumentException("未提供任何可编辑字段用于更新。");
}
String pkParamKey = "primaryKey"; // 主键参数键
params.put(pkParamKey, primaryKeyValue);
String sql = "UPDATE `" + metadata.getTableName() + "` SET " + String.join(", ", sets) + " WHERE `" + metadata.getPrimaryKeyField() + "` = #{" + pkParamKey + "}";
return new SqlTuple(sql, params);
}
/**
* 构建 DELETE SQL。
*
* @param metadata 报表元数据
* @param primaryKeyValue 主键值
* @return 包含 SQL 字符串和参数 Map 的元组
*/
public SqlTuple buildDeleteSql(ReportMetadata metadata, Object primaryKeyValue) {
Map<String, Object> params = new HashMap<>();
String pkParamKey = "primaryKey"; // 主键参数键
params.put(pkParamKey, primaryKeyValue);
String sql = "DELETE FROM `" + metadata.getTableName() + "` WHERE `" + metadata.getPrimaryKeyField() + "` = #{" + pkParamKey + "}";
return new SqlTuple(sql, params);
}
// 辅助类,用于返回 SQL 和参数 Map
public record SqlTuple(String sql, Map<String, Object> params) {}
}
本文由作者按照 CC BY 4.0 进行授权