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

组合模式与动态SQL的完美结合

在软件设计模式中,组合模式(Composite Pattern)是一种将对象组合成树形结构以表示"部分-整体"层次结构的设计模式。MyBatis动态SQL的实现正是组合模式的经典应用,而MixedSqlNode作为这个模式中的组合对象,扮演着至关重要的角色。

组合模式在MyBatis中的体现

组合模式的核心思想是让客户端能够以统一的方式处理单个对象和对象组合。在MyBatis动态SQL的上下文中,这意味着:

  • 叶子节点 :如StaticTextSqlNodeIfSqlNode等,代表最基本的SQL片段

  • 组合节点 :如MixedSqlNode,可以包含其他叶子节点或组合节点

  • 统一接口 :所有节点都实现SqlNode接口,提供统一的apply方法

这种设计使得复杂的SQL生成逻辑能够通过简单的递归调用来实现,极大地简化了系统架构。

目录

MixedSqlNode:动态SQL的骨架

核心实现原理

在动态SQL解析中的角色

组合模式带来的架构优势

[1. 无限嵌套能力](#1. 无限嵌套能力)

[2. 统一的处理接口](#2. 统一的处理接口)

[3. 灵活的可扩展性](#3. 灵活的可扩展性)

实际应用中的解析过程

XML到SqlNode树的转换

解析策略模式

性能优化考虑

[1. 避免过度嵌套](#1. 避免过度嵌套)

[2. 提前终止评估](#2. 提前终止评估)

设计模式的最佳实践启示

[1. 接口设计的简洁性](#1. 接口设计的简洁性)

[2. 上下文对象的精心设计](#2. 上下文对象的精心设计)

[3. 递归思想的巧妙应用](#3. 递归思想的巧妙应用)

总结


🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥 有兴趣可以联系我。文末有免费源码

免费获取源码。

更多内容敬请期待。如有需要可以联系作者免费送

更多源码定制,项目修改,项目二开可以联系作者

点击可以进行搜索(每人免费送一套代码):千套源码目录(点我)

2025元旦源码免费送(点我)

我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。

MixedSqlNode:动态SQL的骨架

核心实现原理

MixedSqlNode的实现看似简单,却蕴含着深刻的设计智慧:

java 复制代码
 public class MixedSqlNode implements SqlNode {
     private final List<SqlNode> contents;
     
     public MixedSqlNode(List<SqlNode> contents) {
         this.contents = contents;
     }
     
     @Override
     public boolean apply(DynamicContext context) {
         for (SqlNode sqlNode : contents) {
             sqlNode.apply(context);
         }
         return true;
     }
 }

设计要点分析:

  1. 透明的组合结构MixedSqlNode对外表现与单个SqlNode完全一致

  2. 递归执行机制 :通过遍历子节点并递归调用apply方法,实现整个SQL树的生成

  3. 始终返回true:由于组合节点包含多个子节点,其执行结果通常为成功

在动态SQL解析中的角色

当MyBatis解析XML配置时,遇到动态SQL标签(如<where><if>等),会递归地构建SqlNode树:

XML 复制代码
 SQL映射文件 → XML解析器 → SqlNode树 → SQL生成

在这个过程中,MixedSqlNode作为容器,承载了同一层级的所有SQL片段,无论是静态文本还是动态条件。

组合模式带来的架构优势

1. 无限嵌套能力

组合模式使得动态SQL支持任意深度的嵌套结构:

XML 复制代码
 <select id="findUsers">
     SELECT * FROM users
     <where>
         <if test="department != null">
             department_id = #{department.id}
             <if test="department.activeOnly">
                 AND status = 'ACTIVE'
                 <choose>
                     <when test="department.includeSubDepts">
                         AND path LIKE CONCAT(#{department.path}, '%')
                     </when>
                     <otherwise>
                         AND parent_id = #{department.id}
                     </otherwise>
                 </choose>
             </if>
         </if>
     </where>
 </select>

对应的SqlNode树结构呈现为:

XML 复制代码
 MixedSqlNode (root)
 ├── StaticTextSqlNode ("SELECT * FROM users")
 └── WhereSqlNode
     └── MixedSqlNode
         └── IfSqlNode (test="department != null")
             └── MixedSqlNode
                 ├── StaticTextSqlNode ("department_id = #{department.id}")
                 └── IfSqlNode (test="department.activeOnly")
                     └── MixedSqlNode
                         ├── StaticTextSqlNode ("AND status = 'ACTIVE'")
                         └── ChooseSqlNode
                             ├── WhenSqlNode (test="department.includeSubDepts")
                             │   └── StaticTextSqlNode ("AND path LIKE...")
                             └── OtherwiseSqlNode
                                 └── StaticTextSqlNode ("AND parent_id =...")

2. 统一的处理接口

由于所有节点都实现SqlNode接口,客户端代码无需关心具体节点类型:

java 复制代码
 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接口即可无缝集成到现有体系中:

java 复制代码
 public class CustomSqlNode implements SqlNode {
     @Override
     public boolean apply(DynamicContext context) {
         // 自定义逻辑
         return true;
     }
 }

实际应用中的解析过程

XML到SqlNode树的转换

MyBatis通过XMLScriptBuilder类完成从XML配置到SqlNode树的转换:

java 复制代码
 public class XMLScriptBuilder {
     public SqlNode parseScriptNode() {
         List<SqlNode> contents = parseDynamicTags(context);
         if (contents.size() == 1) {
             return contents.get(0);
         } else {
             return new MixedSqlNode(contents);
         }
     }
     
     private List<SqlNode> parseDynamicTags(XNode node) {
         List<SqlNode> 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标签,每个标签对应一个SqlNodeHandler

java 复制代码
 private 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. 避免过度嵌套

虽然组合模式支持无限嵌套,但实践中应避免过度复杂的嵌套结构:

XML 复制代码
<!-- 不推荐:过度嵌套 -->
<if test="condition1">
    <if test="condition2">
        <if test="condition3">
            <choose>
                <!-- 更多嵌套 -->
            </choose>
        </if>
    </if>
</if>

<!-- 推荐:适度扁平化 -->
<if test="condition1 and condition2 and condition3">
    <choose>
        <!-- 逻辑 -->
    </choose>
</if>

2. 提前终止评估

某些SqlNode实现了短路逻辑,可以在满足条件时提前终止评估:

java 复制代码
public class ChooseSqlNode implements SqlNode {
    @Override
    public boolean apply(DynamicContext context) {
        for (SqlNode sqlNode : ifSqlNodes) {
            if (sqlNode.apply(context)) {
                return true; // 提前返回
            }
        }
        // ... 其他逻辑
    }
}

设计模式的最佳实践启示

1. 接口设计的简洁性

SqlNode接口的简洁性是其成功的关键:

java 复制代码
public interface SqlNode {
    boolean apply(DynamicContext context);
}

这种极简设计使得实现和理解都变得非常简单。

2. 上下文对象的精心设计

DynamicContext作为贯穿整个组合结构的上下文对象,承载了状态信息和共享数据:

java 复制代码
public class DynamicContext {
    private final StringBuilder sqlBuilder = new StringBuilder();
    private final Map<String, Object> bindings;
    
    public void appendSql(String sql) {
        sqlBuilder.append(sql).append(" ");
    }
    
    public String getSql() {
        return sqlBuilder.toString().trim();
    }
}

3. 递归思想的巧妙应用

组合模式天然适合递归处理,这种思想在SqlNode树的构建和执行中得到了完美体现:

java 复制代码
// 构建时的递归
SqlNode buildSqlNode(XNode node) {
    List<SqlNode> children = buildChildren(node);
    return new MixedSqlNode(children);
}

// 执行时的递归
rootSqlNode.apply(context);

总结

MixedSqlNode作为MyBatis动态SQL实现中的组合对象,虽然代码实现简单,但其背后的设计思想却十分深刻。通过组合模式的应用,MyBatis实现了:

  • 结构清晰:复杂的SQL生成逻辑被分解为简单的树形结构

  • 扩展灵活:新增节点类型不影响现有架构

  • 维护简单:统一的接口降低了理解和维护成本

  • 功能强大:支持任意复杂的嵌套逻辑

这种设计不仅适用于SQL生成场景,对于其他需要处理层次化结构的业务场景也具有重要的参考价值。理解组合模式在MyBatis中的应用,能够帮助我们在自己的项目中更好地进行架构设计,构建出更加灵活、可维护的系统。

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥 有兴趣可以联系我。文末有免费源码

💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!

💖常来我家多看看,
📕网址:
扣棣编程** ,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!**

💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!

往期文章推荐:

基于Springboot + vue实现的学生宿舍信息管理系统
免费获取宠物商城源码--SpringBoot+Vue宠物商城网站系统
【2025小年源码免费送】

⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇点击此处获取源码⬇⬇⬇⬇⬇⬇⬇⬇⬇

相关推荐
sniper_fandc2 小时前
MybatisPlus和pagehelper分页冲突—关于jsqlparser、pagehelper、MybatisPlus三者的版本兼容问题
mybatis·mybatisplus
上官浩仁20 小时前
springboot3 mybatisplus 数据库操作入门与实战
spring boot·mybatis·db
weixin_419658311 天前
MyBatis 进阶
mybatis
祈祷苍天赐我java之术1 天前
Redis 热点数据与冷数据解析
java·redis·mybatis
Roye_ack2 天前
【项目实战 Day9】springboot + vue 苍穹外卖系统(用户端订单模块 + 商家端订单管理模块 完结)
java·vue.js·spring boot·后端·mybatis
人间有清欢2 天前
java数据权限过滤
java·mybatis·权限控制·数据过滤
22jimmy2 天前
MyBatis动态sql
java·开发语言·mybatis
不要再敲了2 天前
SSM框架下的redis使用以及token认证
数据库·spring boot·redis·缓存·mybatis