MyBatis 源码解析:动态 SQL 生成的基本原理

devtools/2024/11/15 0:39:41/

摘要

MyBatis 提供了灵活的动态 SQL 功能,使得开发者可以根据业务需求在运行时生成不同的 SQL 语句。动态 SQL 是 MyBatis 最具特色的功能之一,它允许我们通过条件拼接来生成复杂的查询语句。本文将通过自定义实现一个简化的动态 SQL 生成器,解析其工作原理,并结合 MyBatis 的源码进行深入讲解。


前言

传统的 SQL 查询往往需要开发者根据不同的业务逻辑手动拼接 SQL 语句,而这种方式不仅容易出错,也不够灵活。MyBatis 通过动态 SQL 标签(如 <if><choose><foreach> 等)提供了更灵活的查询方式,允许我们在运行时根据不同条件生成 SQL 语句。本文将通过自定义实现一个简化的动态 SQL 生成器,深入解析其工作原理,并结合 MyBatis 的源码进行详细分析。


自定义实现:动态 SQL 生成器

目标与功能

我们将实现一个简化版的动态 SQL 生成器,支持以下核心功能:

  1. 条件判断:根据输入条件动态拼接 SQL 语句。
  2. 组合条件:支持 ANDOR 等逻辑条件的组合。
  3. 参数绑定:支持 SQL 语句中的参数占位符并绑定实际参数。
  4. 动态拼接:通过链式调用轻松构建复杂 SQL 语句。

实现过程

为了实现一个灵活的动态 SQL 生成器,我们将从以下几个步骤开始:

  1. SQL 语句构建:使用 StringBuilder 构建 SQL 语句片段,并根据不同条件判断是否拼接。
  2. 参数绑定:通过占位符 ? 来处理 SQL 语句中的参数,并动态绑定参数。
  3. 条件判断和组合:实现 ifwhere 条件的判断,确保生成的 SQL 语句语法正确。
  4. 返回生成的 SQL 语句和参数:提供方法返回生成的 SQL 语句和绑定参数列表,供数据库执行使用。
1. 定义 DynamicSqlGenerator 类

首先,我们定义一个 DynamicSqlGenerator 类来生成动态 SQL 语句。这个类将负责根据输入条件动态拼接 SQL 片段,并生成最终的 SQL 语句。

import java.util.ArrayList;
import java.util.List;/*** DynamicSqlGenerator 负责生成动态 SQL 查询语句。* 它通过根据传入的条件拼接 SQL 片段,并支持绑定参数。*/
public class DynamicSqlGenerator {private final StringBuilder sql = new StringBuilder();  // 用于构建 SQL 语句private final List<Object> parameters = new ArrayList<>();  // 存储 SQL 语句绑定的参数/*** 指定要查询的列。* @param columns 查询的列名* @return 当前对象,支持链式调用*/public DynamicSqlGenerator select(String columns) {sql.append("SELECT ").append(columns).append(" FROM ");return this;}/*** 指定要查询的表。* @param tableName 查询的表名* @return 当前对象,支持链式调用*/public DynamicSqlGenerator from(String tableName) {sql.append(tableName);return this;}/*** 添加 WHERE 条件的起点。* @return 当前对象,支持链式调用*/public DynamicSqlGenerator where() {sql.append(" WHERE 1=1");  // 保证 WHERE 后有 1=1 以确保拼接正确return this;}/*** 添加 AND 条件。* @param condition 条件表达式* @param value 参数值* @return 当前对象,支持链式调用*/public DynamicSqlGenerator and(String condition, Object value) {if (value != null) {  // 只有当参数不为空时才拼接条件sql.append(" AND ").append(condition);parameters.add(value);  // 将参数添加到参数列表}return this;}/*** 添加 OR 条件。* @param condition 条件表达式* @param value 参数值* @return 当前对象,支持链式调用*/public DynamicSqlGenerator or(String condition, Object value) {if (value != null) {  // 只有当参数不为空时才拼接条件sql.append(" OR ").append(condition);parameters.add(value);  // 将参数添加到参数列表}return this;}/*** 获取最终生成的 SQL 语句。* @return 生成的 SQL 语句*/public String getSql() {return sql.toString();  // 返回拼接完成的 SQL 语句}/*** 获取 SQL 语句绑定的参数列表。* @return 参数列表*/public List<Object> getParameters() {return parameters;}
}
  • 核心功能
    • select:用于选择查询的列。
    • from:指定要查询的表。
    • where:用于引入 WHERE 条件,默认 1=1 使得后续条件拼接更加灵活。
    • and / or:动态拼接 ANDOR 条件,同时将实际参数值绑定到参数列表中。
    • getSql:返回最终生成的 SQL 语句。
    • getParameters:返回绑定的参数列表。
2. 测试 DynamicSqlGenerator

通过下面的代码来测试 DynamicSqlGenerator 的功能,验证 SQL 语句的生成与参数绑定是否正确。

public class DynamicSqlTest {public static void main(String[] args) {// 创建 SQL 生成器DynamicSqlGenerator generator = new DynamicSqlGenerator();// 构建 SQL 查询,支持根据条件动态生成generator.select("*").from("users").where().and("name = ?", "Alice")  // 只有当 name 非空时才拼接条件.and("age > ?", 25)        // 只有当 age 大于 25 时才拼接条件.or("status = ?", "active");// 打印生成的 SQL 语句System.out.println("Generated SQL: " + generator.getSql());// 打印参数System.out.println("Parameters: " + generator.getParameters());}
}

输出结果

Generated SQL: SELECT * FROM users WHERE 1=1 AND name = ? AND age > ? OR status = ?
Parameters: [Alice, 25, active]
  • SQL 语句生成:程序根据输入条件生成了动态 SQL 语句。
  • 参数绑定:程序生成了 SQL 语句的同时,也返回了绑定的实际参数列表,供执行时使用。
3. 扩展功能

为了增强 SQL 生成器的实用性,我们可以进一步扩展功能:

  1. <choose> 实现:根据多个条件选择其中一个拼接 SQL。
  2. <foreach> 实现:支持批量拼接,比如处理 IN 条件。

这些扩展功能可以帮助我们进一步实现更复杂的动态 SQL 需求。

自定义实现类图

DynamicSqlGenerator
- StringBuilder sql
- List parameters
+select(String columns)
+from(String tableName)
+where()
+and(String condition, Object value)
+or(String condition, Object value)
+getSql()
+getParameters()

代码解析流程图

满足条件
不满足条件
开始
调用select指定查询列
调用from指定表名
调用where添加条件
条件判断
拼接 AND/OR 语句
跳过拼接
拼接完成
返回最终 SQL 语句和参数
结束

源码解析:MyBatis 中的动态 SQL 生成原理

MyBatis 提供了一些节点类(如 IfSqlNodeChooseSqlNode)来解析这些标签。每个 SqlNode 实现都通过反射或者表达式计算,决定是否拼接 SQL 语句。下面我们详细分析 MyBatis 中的动态 SQL 生成流程。

1. SqlNode 的作用

SqlNode 是 MyBatis 中用于动态生成 SQL 片段的基础接口,它为不同的 SQL 节点提供了统一的处理方式。SqlNode 定义了 apply 方法,该方法用于将当前 SQL 节点的内容应用到动态 SQL 上下文中。

public interface SqlNode {boolean apply(DynamicContext context);
}
  • apply 方法:在每个 SqlNode 中,该方法负责将 SQL 节点应用到动态上下文中,并返回是否成功应用。

2. IfSqlNode 的实现

IfSqlNode 是 MyBatis 中处理 <if> 标签的实现类。它的作用是根据表达式的结果决定是否将该节点的内容拼接到最终 SQL 语句中。MyBatis 中通过 ExpressionEvaluator 来判断条件是否满足。

public class IfSqlNode implements SqlNode {private final ExpressionEvaluator evaluator;  // 表达式计算器private final String test;  // 条件表达式private final SqlNode contents;  // 需要执行的 SQL 节点public IfSqlNode(SqlNode contents, String test) {this.evaluator = new ExpressionEvaluator();this.test = test;this.contents = contents;}@Overridepublic boolean apply(DynamicContext context) {// 通过 evaluator 来判断条件表达式是否为 trueif (evaluator.evaluateBoolean(test, context.getBindings())) {contents.apply(context);  // 条件满足时,应用内容节点return true;}return false;}
}
  • apply 方法:通过 ExpressionEvaluator 计算 test 表达式的结果,决定是否拼接该 SQL 片段。
  • evaluator.evaluateBoolean:使用反射和 OGNL 表达式计算条件的真假。

3. DynamicContext 的作用

DynamicContext 是 MyBatis 中用于存储和管理 SQL 片段的核心类。它在动态 SQL 生成的过程中负责记录 SQL 语句的构建过程以及 SQL 语句中需要绑定的参数。

public class DynamicContext {private final Map<String, Object> bindings;  // 绑定参数private final StringBuilder sqlBuilder;  // 用于构建 SQL 语句public DynamicContext(Object parameterObject) {this.bindings = new HashMap<>();this.sqlBuilder = new StringBuilder();this.bindings.put("_parameter", parameterObject);  // 将参数存入上下文}public void appendSql(String sql) {sqlBuilder.append(sql).append(" ");}public String getSql() {return sqlBuilder.toString().trim();}public Map<String, Object> getBindings() {return bindings;}
}
  • appendSql 方法:拼接 SQL 片段到 sqlBuilder 中。
  • getSql 方法:返回最终生成的 SQL 语句。
  • getBindings 方法:获取上下文中存储的参数,用于绑定到 SQL 语句。

4. ChooseSqlNode 的实现

ChooseSqlNode 类用于实现 <choose> 标签,它类似于 Java 中的 switch-case 结构。ChooseSqlNode 通过一组 when 节点来选择最先满足条件的 SQL 片段。

public class ChooseSqlNode implements SqlNode {private final List<SqlNode> whenSqlNodes;private final SqlNode otherwiseSqlNode;public ChooseSqlNode(List<SqlNode> whenSqlNodes, SqlNode otherwiseSqlNode) {this.whenSqlNodes = whenSqlNodes;this.otherwiseSqlNode = otherwiseSqlNode;}@Overridepublic boolean apply(DynamicContext context) {for (SqlNode sqlNode : whenSqlNodes) {if (sqlNode.apply(context)) {return true;  // 如果找到第一个满足条件的 when 节点,应用并返回}}if (otherwiseSqlNode != null) {otherwiseSqlNode.apply(context);  // 如果没有 when 节点满足条件,则应用 otherwise 节点}return true;}
}
  • apply 方法:循环遍历 whenSqlNodes,应用第一个满足条件的 when 节点,如果没有节点满足条件,则应用 otherwise 节点。

总结与互动

通过本文,我们详细探讨了 MyBatis 中动态 SQL 的生成原理,并通过自定义实现了一个简化版的动态 SQL 生成器。动态 SQL 是 MyBatis 中最灵活和强大的功能之一,能够根据运行时的条件动态生成 SQL 语句,极大地提升了 SQL 查询的灵活性和可维护性。

如果你觉得这篇文章对你有帮助,请点赞、收藏并关注本专栏!同时欢迎在评论区留言,分享你的见解和疑问,我们将一起深入探讨!



http://www.ppmy.cn/devtools/110926.html

相关文章

常见的pytest二次开发功能

pytest框架的二次开发主要是为了满足特定的测试需求或扩展其功能。以下是一些常见的pytest二次开发的功能及其实例&#xff0c;以及如何进行开发的大致步骤&#xff1a; 常见的pytest二次开发功能 定制化测试报告&#xff1a; 功能描述&#xff1a;pytest默认生成的测试报告可…

代码随想录八股训练营第三十九天| C++

前言 一、说一下 lambda函数&#xff1f; 1.1.Lambda 函数的一般语法如下: 1.2.捕获子句&#xff1a; 二、C 怎么实现一个单例模式&#xff1f; 2.1.懒汉式&#xff08;线程不安全&#xff09;: 2.2.饿汉式&#xff08;线程安全&#xff09;: 2.3.双重检查锁定&#xff…

问题解决:系统缺失MSVCR100.dll文件

今天在新电脑使用I2C上位机扫描设备时出现了一个小问题&#xff0c;上位机设备在打开后显示警告&#xff1a;系统缺失MSVCR100.dll文件 一眼盯帧&#xff0c;鉴定为缺失了MSVCR100.dll文件。 这里先介绍下.dll文件的作用&#xff0c;.dll文件&#xff0c;即动态链接库文件&am…

for循环+fork-join_none的隐蔽漏洞

1. 翻车现场 比如某个场景中我们希望用for循环和fork嵌套开辟循环的线程&#xff0c;打印出10个选美者的编号和等级&#xff0c;代码很可能是这样实现的&#xff1a; for (int i0 ; i<10; i) beginfork$display(“No%0d&#xff0c;My face_grade is %0d”, i &#xff0c…

【苍穹外卖】前端 Day 1

1 Vue 1.1 通过 vue cli 脚手架创建前端工程 1.2 项目结构 1.3 启动项目 VS Code 启动前端项目&#xff1a; npm run serve 注意这里占用端口号 8080 与 java springboot 占用端口号一致&#xff0c;有冲突 serve 是这个名字 终止&#xff1a;ctrl c 修改端口号 2 vue 基本…

MyBatis-Plus 与 Mockito:解决 Lambda 缓存初始化问题

问题背景 MyBatis-Plus 提供了便捷的 Lambda 查询表达式&#xff0c;但它依赖于实体类与数据库表的映射缓存。如果在测试环境中&#xff0c;这些映射未正确初始化&#xff0c;可能导致 can not find lambda cache for this entity 异常。这一问题特别容易在与 Mockito 搭配使用…

Chai-1:药物分子结构预测的新型多模态基础模型

参考: https://www.chaidiscovery.com/blog/introducing-chai-1 https://github.com/chaidiscovery/chai-lab https://chaiassets.com/chai-1/paper/technical_report_v1.pdf 这是一种用于分子结构预测的新型多模态基础模型,可在与药物发现相关的各种任务中执行最先进的功能。…

安装Anaconda(过程)

Anaconda是一个开源的Python发行版本&#xff0c;用来管理Python相关的包&#xff0c;安装Anaconda可以很方便的切换不同的环境&#xff0c;使用不同的深度学习框架开发项目&#xff0c;本文将详细介绍Anaconda的安装。 一、安装 1、安装方式 官网&#xff1a;“https://www.…