- 核心思路概述 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
// 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) {}
}