手写MyBatis第85弹:组合模式在SqlNode设计中的精妙应用

发布时间:2026/10/14 2:05:42

手写MyBatis第85弹:组合模式在SqlNode设计中的精妙应用 组合模式与动态SQL的完美结合在软件设计模式中组合模式Composite Pattern是一种将对象组合成树形结构以表示部分-整体层次结构的设计模式。MyBatis动态SQL的实现正是组合模式的经典应用而MixedSqlNode作为这个模式中的组合对象扮演着至关重要的角色。组合模式在MyBatis中的体现组合模式的核心思想是让客户端能够以统一的方式处理单个对象和对象组合。在MyBatis动态SQL的上下文中这意味着叶子节点如StaticTextSqlNode、IfSqlNode等代表最基本的SQL片段组合节点如MixedSqlNode可以包含其他叶子节点或组合节点统一接口所有节点都实现SqlNode接口提供统一的apply方法这种设计使得复杂的SQL生成逻辑能够通过简单的递归调用来实现极大地简化了系统架构。目录MixedSqlNode动态SQL的骨架核心实现原理在动态SQL解析中的角色组合模式带来的架构优势1. 无限嵌套能力2. 统一的处理接口3. 灵活的可扩展性实际应用中的解析过程XML到SqlNode树的转换解析策略模式性能优化考虑1. 避免过度嵌套2. 提前终止评估设计模式的最佳实践启示1. 接口设计的简洁性2. 上下文对象的精心设计3. 递归思想的巧妙应用总结(❁´◡❁)您的点赞➕评论➕收藏⭐是作者创作的最大动力 支持我点赞收藏⭐️留言欢迎留言讨论我们常常在当下感到时间慢觉得未来遥远但一旦回头看时间已经悄然流逝。对于未来尽管如此也应该保持一种从容的态度相信未来仍有许多可能性等待着我们。MixedSqlNode动态SQL的骨架核心实现原理MixedSqlNode的实现看似简单却蕴含着深刻的设计智慧public class MixedSqlNode implements SqlNode { private final ListSqlNode contents; public MixedSqlNode(ListSqlNode contents) { this.contents contents; } Override public boolean apply(DynamicContext context) { for (SqlNode sqlNode : contents) { sqlNode.apply(context); } return true; } }设计要点分析透明的组合结构MixedSqlNode对外表现与单个SqlNode完全一致递归执行机制通过遍历子节点并递归调用apply方法实现整个SQL树的生成始终返回true由于组合节点包含多个子节点其执行结果通常为成功在动态SQL解析中的角色当MyBatis解析XML配置时遇到动态SQL标签如where、if等会递归地构建SqlNode树SQL映射文件 → XML解析器 → SqlNode树 → SQL生成在这个过程中MixedSqlNode作为容器承载了同一层级的所有SQL片段无论是静态文本还是动态条件。组合模式带来的架构优势1. 无限嵌套能力组合模式使得动态SQL支持任意深度的嵌套结构select idfindUsers SELECT * FROM users where if testdepartment ! null department_id #{department.id} if testdepartment.activeOnly AND status ACTIVE choose when testdepartment.includeSubDepts AND path LIKE CONCAT(#{department.path}, %) /when otherwise AND parent_id #{department.id} /otherwise /choose /if /if /where /select对应的SqlNode树结构呈现为MixedSqlNode (root) ├── StaticTextSqlNode (SELECT * FROM users) └── WhereSqlNode └── MixedSqlNode └── IfSqlNode (testdepartment ! null) └── MixedSqlNode ├── StaticTextSqlNode (department_id #{department.id}) └── IfSqlNode (testdepartment.activeOnly) └── MixedSqlNode ├── StaticTextSqlNode (AND status ACTIVE) └── ChooseSqlNode ├── WhenSqlNode (testdepartment.includeSubDepts) │ └── StaticTextSqlNode (AND path LIKE...) └── OtherwiseSqlNode └── StaticTextSqlNode (AND parent_id ...)2. 统一的处理接口由于所有节点都实现SqlNode接口客户端代码无需关心具体节点类型public class DynamicSqlSource implements SqlSource { private final SqlNode rootSqlNode; public BoundSql getBoundSql(Object parameterObject) { DynamicContext context new DynamicContext(parameterObject); rootSqlNode.apply(context); // 统一接口调用 // ... 后续处理 } }这种统一性大大降低了代码的复杂度和维护成本。3. 灵活的可扩展性新增SQL节点类型时只需实现SqlNode接口即可无缝集成到现有体系中public class CustomSqlNode implements SqlNode { Override public boolean apply(DynamicContext context) { // 自定义逻辑 return true; } }实际应用中的解析过程XML到SqlNode树的转换MyBatis通过XMLScriptBuilder类完成从XML配置到SqlNode树的转换public class XMLScriptBuilder { public SqlNode parseScriptNode() { ListSqlNode contents parseDynamicTags(context); if (contents.size() 1) { return contents.get(0); } else { return new MixedSqlNode(contents); } } private ListSqlNode parseDynamicTags(XNode node) { ListSqlNode contents new ArrayList(); NodeList children node.getNode().getChildNodes(); for (int i 0; i children.getLength(); i) { XNode child node.newXNode(children.item(i)); if (child.getNode().getNodeType() Node.TEXT_NODE) { String data child.getStringBody(); if (data ! null data.trim().length() 0) { contents.add(new StaticTextSqlNode(data)); } } else if (child.getNode().getNodeType() Node.ELEMENT_NODE) { String nodeName child.getNode().getNodeName(); SqlNodeHandler handler nodeHandlers.get(nodeName); if (handler ! null) { handler.handleNode(child, contents); } } } return contents; } }解析策略模式MyBatis采用策略模式来处理不同的动态SQL标签每个标签对应一个SqlNodeHandlerprivate void initNodeHandlerMap() { nodeHandlers.put(if, new IfHandler()); nodeHandlers.put(trim, new TrimHandler()); nodeHandlers.put(where, new WhereHandler()); nodeHandlers.put(set, new SetHandler()); nodeHandlers.put(foreach, new ForEachHandler()); nodeHandlers.put(choose, new ChooseHandler()); // ... 其他处理器 }性能优化考虑1. 避免过度嵌套虽然组合模式支持无限嵌套但实践中应避免过度复杂的嵌套结构!-- 不推荐过度嵌套 -- if testcondition1 if testcondition2 if testcondition3 choose !-- 更多嵌套 -- /choose /if /if /if !-- 推荐适度扁平化 -- if testcondition1 and condition2 and condition3 choose !-- 逻辑 -- /choose /if2. 提前终止评估某些SqlNode实现了短路逻辑可以在满足条件时提前终止评估public class ChooseSqlNode implements SqlNode { Override public boolean apply(DynamicContext context) { for (SqlNode sqlNode : ifSqlNodes) { if (sqlNode.apply(context)) { return true; // 提前返回 } } // ... 其他逻辑 } }设计模式的最佳实践启示1. 接口设计的简洁性SqlNode接口的简洁性是其成功的关键public interface SqlNode { boolean apply(DynamicContext context); }这种极简设计使得实现和理解都变得非常简单。2. 上下文对象的精心设计DynamicContext作为贯穿整个组合结构的上下文对象承载了状态信息和共享数据public class DynamicContext { private final StringBuilder sqlBuilder new StringBuilder(); private final MapString, Object bindings; public void appendSql(String sql) { sqlBuilder.append(sql).append( ); } public String getSql() { return sqlBuilder.toString().trim(); } }3. 递归思想的巧妙应用组合模式天然适合递归处理这种思想在SqlNode树的构建和执行中得到了完美体现// 构建时的递归 SqlNode buildSqlNode(XNode node) { ListSqlNode children buildChildren(node); return new MixedSqlNode(children); } // 执行时的递归 rootSqlNode.apply(context);总结MixedSqlNode作为MyBatis动态SQL实现中的组合对象虽然代码实现简单但其背后的设计思想却十分深刻。通过组合模式的应用MyBatis实现了结构清晰复杂的SQL生成逻辑被分解为简单的树形结构扩展灵活新增节点类型不影响现有架构维护简单统一的接口降低了理解和维护成本功能强大支持任意复杂的嵌套逻辑这种设计不仅适用于SQL生成场景对于其他需要处理层次化结构的业务场景也具有重要的参考价值。理解组合模式在MyBatis中的应用能够帮助我们在自己的项目中更好地进行架构设计构建出更加灵活、可维护的系统。(❁´◡❁)您的点赞➕评论➕收藏⭐是作者创作的最大动力 支持我点赞收藏⭐️留言欢迎留言讨论
延伸阅读

更多相关文章

2026/10/14 2:05:42

手写MyBatis第84弹:深入解析MyBatis动态SQL:手写实现核心SqlNode节点

动态SQL的重要性与实现原理 在现代Java持久层框架中,动态SQL是处理复杂查询条件的核心功能。MyBatis通过灵活的XML配置和强大的动态SQL能力,让开发者能够优雅地处理各种复杂的数据库操作场景。本文将深入剖析MyBatis动态SQL的内部实现机制,特…

2026/10/14 2:05:42

LTspice中文快速键一览表:从入门到肌肉记忆的键位指南

简介:这份LTspice中文快速键一览表面向电子技术与电磁学领域的电路设计初学者和仿真工程师,帮助解决软件操作不熟练、快捷键记忆零散、仿真分析效率低等问题。资源以PDF形式呈现,压缩包内共1个文件,大小约385KB,内容围…

2026/10/14 2:05:42

新浪Level2接口SDK:Python获取十档行情与盘口异动监控实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 3:10:45

Hive行级更新实战:ACID事务与ORC分桶表配置指南

1. 从一次数据修正需求说起:为什么Hive的行级更新是个真问题做数据仓库的人大概率都遇到过这种场景:上游业务系统在凌晨批量同步数据时,某几条订单的状态字段写错了,或者某个用户的手机号录入了脏数据。放在MySQL里,一…

2026/10/14 3:10:45

多平台自动签到脚本搭建实录:青龙面板从零跑通

前言 签到这件事,手动点一个月真的会腻。这篇记录我从零搭起青龙面板、把多个平台的每日签到全部交给脚本自动完成的完整过程,新手照做半小时能跑通。## 一、为什么要用青龙面板先说结论:青龙面板是目前最省心的定时任务管理工具,…

2026/10/14 3:10:45

van-empty 组件实战:从空白页到友好空状态的设计与调优

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 3:05:45

Kylin 3.3探针安装调试实战:从依赖检查到性能避坑

简介:这份《Kylin 3.3 探针软件安装调试手册》面向麒麟操作系统运维人员、安全监测实施工程师及需要部署探针的现场技术人员,用于解决探针软件从安装、配置到卸载全流程缺少规范指引的问题。资源包内共1个docx文档,压缩包约1002KB&#xff0c…

2026/10/13 5:19:27

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/13 17:09:18

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑