当前位置: 首页 > news >正文

[笔记] 动态 SQL 查询技术解析:构建灵活高效的企业级数据访问层

文章目录

  • 一. 应用场景
  • 二. 使用示例
    • 示例1
    • 示例2
    • 示例3
  • 三. 实现
    • 1. 动态表查询构建器,模仿MyBatis-Plus
    • 2. mapper
    • 3. mapper.xml
      • 功能概述
      • 参数说明
  • 四. 动态 SQL 的优化与风险防控


在企业级应用开发中,数据查询场景往往呈现出复杂多变的特点 —— 从简单的单表筛选到包含嵌套条件的多维度检索,从固定格式的报表查询到用户自定义的灵活搜索。动态 SQL 查询以其 “参数化配置 + 逻辑动态拼接” 的特性,成为构建通用数据访问层的核心技术。以下从应用场景、技术优势及工程实践三个维度,深度解析这一技术的实用价值与优化方向。


一. 应用场景

这种动态 SQL 查询在通用数据访问层中非常实用,例如:

  • 通用的数据查询服务。
  • 支持复杂筛选条件的报表系统。
  • 数据管理后台的高级搜索功能。

二. 使用示例

示例1

在这里插入图片描述

示例2

在这里插入图片描述

示例3

在这里插入图片描述


三. 实现

1. 动态表查询构建器,模仿MyBatis-Plus

/*** 动态表查询构建器,模仿MyBatis-Plus** @author 鲁子狄* @since 2025/07/11 10:43*/
@Data
public class DynamicQueryWrapper {private final Map<String, Object> param = new HashMap<>();private final List<QueryCondition> conditions = new ArrayList<>();private final List<String> notNullColumns = new ArrayList<>();private final List<String> nullColumns = new ArrayList<>();private String tableName;private String selectColumns;private Object year;private Object versionId;private String groupByField;private String orderByField;private String sort = "asc";/*** 设置表名*/public DynamicQueryWrapper table(String tableName) {this.tableName = tableName;param.put("bmc", tableName);return this;}/*** 设置要查询的字段*/public DynamicQueryWrapper select(String columns) {selectColumns = columns;param.put("bzd", columns);return this;}/*** 设置年份*/public DynamicQueryWrapper year(Object year) {this.year = year;param.put("year", year);return this;}/*** 设置版本ID*/public DynamicQueryWrapper versionId(Object versionId) {this.versionId = versionId;param.put("versionId", versionId);return this;}/*** 等于条件*/public DynamicQueryWrapper eq(boolean condition, String column, Object val) {if (condition && column != null && val != null) {conditions.add(new QueryCondition(column, "=", val));}return this;}/*** 等于条件*/public DynamicQueryWrapper eq(String column, Object val) {return eq(true, column, val);}/*** 不等于条件*/public DynamicQueryWrapper ne(boolean condition, String column, Object val) {if (condition && column != null && val != null) {conditions.add(new QueryCondition(column, "<>", val));}return this;}/*** 不等于条件*/public DynamicQueryWrapper ne(String column, Object val) {return ne(true, column, val);}/*** 大于条件*/public DynamicQueryWrapper gt(boolean condition, String column, Object val) {if (condition && column != null && val != null) {conditions.add(new QueryCondition(column, ">", val));}return this;}/*** 大于条件*/public DynamicQueryWrapper gt(String column, Object val) {return gt(true, column, val);}/*** 小于条件*/public DynamicQueryWrapper lt(boolean condition, String column, Object val) {if (condition && column != null && val != null) {conditions.add(new QueryCondition(column, "<", val));}return this;}/*** 小于条件*/public DynamicQueryWrapper lt(String column, Object val) {return lt(true, column, val);}/*** 大于等于条件*/public DynamicQueryWrapper ge(boolean condition, String column, Object val) {if (condition && column != null && val != null) {conditions.add(new QueryCondition(column, ">=", val));}return this;}/*** 大于等于条件*/public DynamicQueryWrapper ge(String column, Object val) {return ge(true, column, val);}/*** 小于等于条件*/public DynamicQueryWrapper le(boolean condition, String column, Object val) {if (condition && column != null && val != null) {conditions.add(new QueryCondition(column, "<=", val));}return this;}/*** 小于等于条件*/public DynamicQueryWrapper le(String column, Object val) {return le(true, column, val);}/*** 模糊查询*/public DynamicQueryWrapper like(boolean condition, String column, Object val) {if (condition && column != null && val != null) {conditions.add(new QueryCondition(column, "LIKE", val, true));}return this;}/*** 模糊查询*/public DynamicQueryWrapper like(String column, Object val) {return like(true, column, val);}/*** 左模糊查询*/public DynamicQueryWrapper likeLeft(boolean condition, String column, Object val) {if (condition && column != null && val != null) {conditions.add(new QueryCondition(column, "LIKE_LEFT", val, true));}return this;}/*** 左模糊查询*/public DynamicQueryWrapper likeLeft(String column, Object val) {return likeLeft(true, column, val);}/*** 右模糊查询*/public DynamicQueryWrapper likeRight(boolean condition, String column, Object val) {if (condition && column != null && val != null) {conditions.add(new QueryCondition(column, "LIKE_RIGHT", val, true));}return this;}/*** 右模糊查询*/public DynamicQueryWrapper likeRight(String column, Object val) {return likeRight(true, column, val);}/*** 不包含查询*/public DynamicQueryWrapper notLike(boolean condition, String column, Object val) {if (condition && column != null && val != null) {conditions.add(new QueryCondition(column, "NOT LIKE", val, true));}return this;}/*** 不包含查询*/public DynamicQueryWrapper notLike(String column, Object val) {return notLike(true, column, val);}/*** IN查询*/public DynamicQueryWrapper in(boolean condition, String column, Collection<?> values) {if (condition && column != null && values != null && !values.isEmpty()) {conditions.add(new QueryCondition(column, "IN", new ArrayList<>(values)));}return this;}/*** IN查询*/public DynamicQueryWrapper in(String column, Collection<?> values) {return in(true, column, values);}/*** NOT IN查询*/public DynamicQueryWrapper notIn(boolean condition, String column, Collection<?> values) {if (condition && column != null && values != null && !values.isEmpty()) {conditions.add(new QueryCondition(column, "NOT IN", new ArrayList<>(values)));}return this;}/*** NOT IN查询*/public DynamicQueryWrapper notIn(String column, Collection<?> values) {return notIn(true, column, values);}/*** BETWEEN查询*/public DynamicQueryWrapper between(boolean condition, String column, Object val1, Object val2) {if (condition && column != null && val1 != null && val2 != null) {List<Object> values = new ArrayList<>();values.add(val1);values.add(val2);conditions.add(new QueryCondition(column, "BETWEEN", values));}return this;}/*** BETWEEN查询*/public DynamicQueryWrapper between(String column, Object val1, Object val2) {return between(true, column, val1, val2);}/*** NOT BETWEEN查询*/public DynamicQueryWrapper notBetween(boolean condition, String column, Object val1, Object val2) {if (condition && column != null && val1 != null && val2 != null) {List<Object> values = new ArrayList<>();values.add(val1);values.add(val2);conditions.add(new QueryCondition(column, "NOT BETWEEN", values));}return this;}/*** NOT BETWEEN查询*/public DynamicQueryWrapper notBetween(String column, Object val1, Object val2) {return notBetween(true, column, val1, val2);}/*** 字段为NULL*/public DynamicQueryWrapper isNull(boolean condition, String column) {if (condition && column != null) {nullColumns.add(column);}return this;}/*** 字段为NULL*/public DynamicQueryWrapper isNull(String column) {return isNull(true, column);}/*** 字段不为NULL*/public DynamicQueryWrapper isNotNull(boolean condition, String column) {if (condition && column != null) {notNullColumns.add(column);}return this;}/*** 字段不为NULL*/public DynamicQueryWrapper isNotNull(String column) {return isNotNull(true, column);}/*** 分组*/public DynamicQueryWrapper groupBy(String field) {groupByField = field;return this;}/*** 排序*/public DynamicQueryWrapper orderBy(String field, boolean isAsc) {orderByField = field;sort = isAsc ? "asc" : "desc";return this;}/*** 正序排列*/public DynamicQueryWrapper orderByAsc(String field) {return orderBy(field, true);}/*** 倒序排列*/public DynamicQueryWrapper orderByDesc(String field) {return orderBy(field, false);}/*** OR条件*/public DynamicQueryWrapper or(boolean condition, Consumer<DynamicQueryWrapper> consumer) {if (condition) {DynamicQueryWrapper nestedWrapper = new DynamicQueryWrapper();consumer.accept(nestedWrapper);// 创建OR组ConditionGroup orGroup = new ConditionGroup();orGroup.getConditions().addAll(nestedWrapper.conditions);orGroup.setLogic("OR");conditions.add(orGroup);}return this;}/*** OR条件*/public DynamicQueryWrapper or(Consumer<DynamicQueryWrapper> consumer) {return or(true, consumer);}/*** AND条件*/public DynamicQueryWrapper and(boolean condition, Consumer<DynamicQueryWrapper> consumer) {if (condition) {DynamicQueryWrapper nestedWrapper = new DynamicQueryWrapper();consumer.accept(nestedWrapper);// 创建AND组ConditionGroup andGroup = new ConditionGroup();andGroup.getConditions().addAll(nestedWrapper.conditions);andGroup.setLogic("AND");conditions.add(andGroup);}return this;}/*** AND条件*/public DynamicQueryWrapper and(Consumer<DynamicQueryWrapper> consumer) {return and(true, consumer);}/*** 查询条件*/@Datapublic static class QueryCondition {private final String column;private final String operator;private final Object value;private final List<QueryCondition> children = new ArrayList<>();private boolean isLike = false;QueryCondition(String column, String operator, Object value) {this.column = column;this.operator = operator;this.value = value;}QueryCondition(String column, String operator, Object value, boolean isLike) {this.column = column;this.operator = operator;this.value = value;this.isLike = isLike;}// 辅助方法,避免在XML中直接比较字符串public boolean isEqualOperator() {return "=".equals(operator);}public boolean isNotEqualOperator() {return "<>".equals(operator);}public boolean isGreaterThanOperator() {return ">".equals(operator);}public boolean isLessThanOperator() {return "<".equals(operator);}public boolean isGreaterEqualOperator() {return ">=".equals(operator);}public boolean isLessEqualOperator() {return "<=".equals(operator);}public boolean isLikeOperator() {return "LIKE".equals(operator);}public boolean isLeftLikeOperator() {return "LIKE_LEFT".equals(operator);}public boolean isRightLikeOperator() {return "LIKE_RIGHT".equals(operator);}public boolean isNotLikeOperator() {return "NOT LIKE".equals(operator);}public boolean isInOperator() {return "IN".equals(operator);}public boolean isNotInOperator() {return "NOT IN".equals(operator);}public boolean isBetweenOperator() {return "BETWEEN".equals(operator);}public boolean isNotBetweenOperator() {return "NOT BETWEEN".equals(operator);}public boolean isComparisonOperator() {return isEqualOperator() || isNotEqualOperator() || isGreaterThanOperator() || isLessThanOperator()|| isGreaterEqualOperator() || isLessEqualOperator();}public boolean isGroup() {return false;}}/*** 条件组(用于处理括号内的AND/OR组合)*/@Getter@Setterpublic static class ConditionGroup extends QueryCondition {private final List<QueryCondition> conditions = new ArrayList<>();private String logic = "AND";ConditionGroup() {super(null, null, null);}// 添加辅助方法,判断逻辑类型public boolean isAndLogic() {return "AND".equals(logic);}public boolean isOrLogic() {return "OR".equals(logic);}@Overridepublic boolean isGroup() {return true;}}
}

2. mapper

	/*** selectList 使用QueryWrapper进行查询** @param wrapper 自定义查询条件* @return java.util.List<java.util.Map < java.lang.String, java.lang.String>>*/List<Map<String, String>> selectDataList(@Param("wrapper") DynamicQueryWrapper wrapper);

3. mapper.xml

<select id="selectDataList" resultType="java.util.Map">SELECTCAST(id as char) as sjid, ${wrapper.param.bzd}, #{wrapper.param.year} as year, version_id as versionIdFROM${wrapper.param.bmc}WHERE 1=1<if test="wrapper.param.versionId != null and wrapper.param.versionId != ''">AND version_id = #{wrapper.param.versionId}</if><!-- 处理所有条件 --><if test="wrapper.conditions != null and wrapper.conditions.size() > 0"><foreach collection="wrapper.conditions" item="condition" index="index"><choose><!-- 处理条件组(带括号的AND/OR条件) --><when test="condition.isGroup()">AND (<trim prefixOverrides="AND|OR"><foreach collection="condition.conditions" item="groupItem">${condition.logic}<choose><when test="groupItem.comparisonOperator">${groupItem.column} ${groupItem.operator} #{groupItem.value}</when><when test="groupItem.likeOperator and groupItem.isLike">${groupItem.column} LIKE CONCAT('%', #{groupItem.value}, '%')</when><when test="groupItem.leftLikeOperator and groupItem.isLike">${groupItem.column} LIKE CONCAT('%', #{groupItem.value})</when><when test="groupItem.rightLikeOperator and groupItem.isLike">${groupItem.column} LIKE CONCAT(#{groupItem.value}, '%')</when><when test="groupItem.notLikeOperator and groupItem.isLike">${groupItem.column} NOT LIKE CONCAT('%', #{groupItem.value}, '%')</when><when test="groupItem.inOperator">${groupItem.column} IN<foreach collection="groupItem.value" item="val" open="(" separator="," close=")">#{val}</foreach></when><when test="groupItem.notInOperator">${groupItem.column} NOT IN<foreach collection="groupItem.value" item="val" open="(" separator="," close=")">#{val}</foreach></when><when test="groupItem.betweenOperator">${groupItem.column} BETWEEN #{groupItem.value[0]} AND #{groupItem.value[1]}</when><when test="groupItem.notBetweenOperator">${groupItem.column} NOT BETWEEN #{groupItem.value[0]} AND #{groupItem.value[1]}</when></choose></foreach></trim>)</when><!-- 处理普通条件 --><otherwise><choose><when test="condition.comparisonOperator">AND ${condition.column} ${condition.operator} #{condition.value}</when><when test="condition.likeOperator and condition.isLike">AND ${condition.column} LIKE CONCAT('%', #{condition.value}, '%')</when><when test="condition.leftLikeOperator and condition.isLike">AND ${condition.column} LIKE CONCAT('%', #{condition.value})</when><when test="condition.rightLikeOperator and condition.isLike">AND ${condition.column} LIKE CONCAT(#{condition.value}, '%')</when><when test="condition.notLikeOperator and condition.isLike">AND ${condition.column} NOT LIKE CONCAT('%', #{condition.value}, '%')</when><when test="condition.inOperator">AND ${condition.column} IN<foreach collection="condition.value" item="val" open="(" separator="," close=")">#{val}</foreach></when><when test="condition.notInOperator">AND ${condition.column} NOT IN<foreach collection="condition.value" item="val" open="(" separator="," close=")">#{val}</foreach></when><when test="condition.betweenOperator">AND ${condition.column} BETWEEN #{condition.value[0]} AND #{condition.value[1]}</when><when test="condition.notBetweenOperator">AND ${condition.column} NOT BETWEEN #{condition.value[0]} AND #{condition.value[1]}</when></choose></otherwise></choose></foreach></if><!-- 处理NULL条件 --><if test="wrapper.nullColumns != null and wrapper.nullColumns.size() > 0"><foreach collection="wrapper.nullColumns" item="column">AND ${column} IS NULL</foreach></if><!-- 处理非空条件 --><if test="wrapper.notNullColumns != null and wrapper.notNullColumns.size() > 0"><foreach collection="wrapper.notNullColumns" item="column">AND ${column} IS NOT NULL AND ${column} != ''</foreach></if><!-- 处理分组 --><if test="wrapper.groupByField != null and wrapper.groupByField != ''">GROUP BY ${wrapper.groupByField}</if><!-- 处理排序 --><if test="wrapper.orderByField != null and wrapper.orderByField != ''">ORDER BY ${wrapper.orderByField} ${wrapper.sort}</if></select>

功能概述

  1. 基础查询:从指定表(${wrapper.param.bmc})中选取数据,并返回一个 Map 结果集。
  2. 动态条件过滤:能根据不同的条件动态生成 WHERE 子句,支持多种比较操作,像等值比较、LIKE 模糊查询、IN/NOT IN 集合查询以及 BETWEEN 范围查询等。
  3. 条件分组:支持用括号将条件分组,以此来实现复杂的逻辑组合,例如 (A AND B) OR C
  4. 空值判断:可以筛选出 NULL 或非 NULL 的字段。
  5. 分组和排序:支持 GROUP BYORDER BY 子句,能对查询结果进行分组和排序。

参数说明

查询使用了一个名为wrapper的参数对象,该对象包含以下属性:

  • param.bzd:要查询的字段列表。
  • param.bmc:要查询的表名。
  • param.year:年份参数。
  • param.versionId:版本 ID,用于筛选特定版本。
  • conditions:条件列表,包含简单条件和条件组。
  • nullColumns:需要判断为 NULL 的字段列表。
  • notNullColumns:需要判断为非 NULL 的字段列表。
  • groupByField:用于分组的字段。
  • orderByFieldsort:用于排序的字段和排序方向。

四. 动态 SQL 的优化与风险防控

现系统不用

对表名(${wrapper.param.bmc})、字段名(${wrapper.param.bzd})等拼接参数进行白名单校验,避免恶意用户注入非法表名(如user; DROP TABLE data)。

// 表名白名单校验
private static final Set<String> TABLE_WHITELIST = new HashSet<>(Arrays.asList("user", "order", "product"));
if (!TABLE_WHITELIST.contains(wrapper.getParam().getBmc())) {throw new SecurityException("非法表名查询");
}
http://www.dtcms.com/a/279070.html

相关文章:

  • 131. Java 泛型 - 目标类型与泛型推断
  • VUE3 添加长按手势
  • Nacos 技术研究文档(基于 Nacos 3)
  • 国内MCP服务器搜索引擎有哪些?MCP导航站平台推荐
  • Docker配置国内镜像源
  • SpringBoot整合MongoDB
  • 链表算法之【获取链表开始入环的节点】
  • Codeforces Round 1019 (Div. 2) A-D
  • Windows下安装nvm管理多个版本的node.js
  • 在项目中集成开源的表单设计器FcDesigner源码
  • 「源力觉醒 创作者计划」_文心开源模型(ERNIE-4.5-VL-28B-A3B-PT)使用心得
  • linux系统下Ollama 模型下载出现 “no space left on device” 错误(非Docker)怎么删缓存/没下完的模型?
  • Docker入门基础
  • Vue+axios
  • Cadence操作说明
  • STM32固件升级设计——SD卡升级固件
  • 论文阅读:WildGS-SLAM:Monocular Gaussian Splatting SLAM in Dynamic Environments
  • 如何在 React + TypeScript 中实现 JSON 格式化功能
  • 【React Natve】NetworkError 和 TouchableOpacity 组件
  • MySQL的可重复读隔离级别实现原理分析
  • 银河麒麟KYSEC安全机制详解
  • 在echarts中legend中设置rich中第二项的宽度不起作用的处理方案。
  • PyTorch张量(Tensor)创建的方式汇总详解和代码示例
  • 鸿蒙Next开发,配置Navigation的Route
  • 26-计组-多处理器
  • HCIA认证核心知识体系全解析
  • OpenCV计算机视觉实战(15)——霍夫变换详解
  • Mars3d的走廊只能在一个平面的无法折叠的解决方案
  • UNet 改进(36):引入ELAN-FR模块(通道注意力+空间注意力)
  • 华为OD 消消乐游戏