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

深入解析MyBatis中#{}和${}的区别与应用场景

在MyBatis框架的使用过程中,SQL映射文件的编写是核心工作之一。而#{}和${}这两种参数占位符语法,虽然看起来相似,却有着本质的区别。正确理解和使用它们,不仅关系到应用程序的安全性,还会影响系统性能。本文将全面剖析这两种语法的区别、实现原理、使用场景以及最佳实践。

一、基本概念与语法

1.1 #{}语法

#{}是MyBatis中的预编译占位符,也称为参数标记。它的基本形式如下:

<select id="findUserById" resultType="User">SELECT * FROM users WHERE id = #{userId}
</select>

1.2 ${}语法

${}是MyBatis中的字符串替换占位符,也称为非转义字符串替换。它的基本形式如下:

<select id="findUsersByTable" resultType="User">SELECT * FROM ${tableName} WHERE status = 1
</select>

二、底层实现原理

2.1 #{}的工作原理

当MyBatis遇到#{}时,会进行以下处理:

  1. 解析阶段:MyBatis解析SQL映射文件时,识别出#{}标记

  2. 参数处理:运行时将参数值通过PreparedStatement的set方法设置

  3. SQL生成:最终生成带有"?"的预编译SQL语句

例如上面的例子会生成:

SELECT * FROM users WHERE id = ?

然后通过PreparedStatement的setInt/setString等方法设置参数值。

2.2 ${}的工作原理

对于${}的处理则完全不同:

  1. 直接替换:MyBatis在SQL解析阶段就直接将${}替换为实际的参数值

  2. 字符串拼接:替换后的SQL语句是通过字符串拼接而成的

  3. 直接执行:最终生成的是完整的SQL语句,而非预编译语句

例如,如果tableName="users",生成的SQL就是:

SELECT * FROM users WHERE status = 1

三、核心区别对比

3.1 安全性对比

特性#{}${}
SQL注入防护安全,能防止SQL注入不安全,存在SQL注入风险
实现方式参数化查询字符串拼接

#{}示例:

String sql = "SELECT * FROM users WHERE name = #{name}";
// 即使用户输入 name = "' OR '1'='1"
// 实际执行:SELECT * FROM users WHERE name = ? 
// 参数值会被正确处理,不会导致注入

${}示例:

String sql = "SELECT * FROM users WHERE name = '${name}'";
// 如果用户输入 name = "' OR '1'='1"
// 实际执行:SELECT * FROM users WHERE name = '' OR '1'='1'
// 这将返回所有用户数据,造成SQL注入

3.2 性能对比

特性#{}${}
数据库优化支持预编译,可缓存执行计划每次都是新SQL,无法缓存
网络传输只需传输参数需要传输完整SQL
编译次数一次编译多次执行每次都需要重新编译

3.3 使用场景对比

场景#{}${}说明
普通参数值推荐使用#{}
表名动态表名必须使用${}
列名动态列名必须使用${}
ORDER BY子句动态排序需谨慎使用
GROUP BY子句动态分组需谨慎使用
LIKE模糊查询需特殊处理通配符

四、深入应用场景

4.1 必须使用#{}的场景

  1. 所有用户输入的参数值

    WHERE username = #{username} AND password = #{password}
  2. 数值型参数

    WHERE age > #{minAge} AND age < #{maxAge}
  3. 日期型参数

    WHERE create_time > #{startDate}

4.2 可能需要使用${}的场景

  1. 动态表名 

    SELECT * FROM ${tableName}

    适用于分表场景,如表名按年份分表:user_2022, user_2023等

  2. 动态列名 

    SELECT ${columns} FROM users

    适用于动态选择返回字段的场景

  3. ORDER BY排序 

    ORDER BY ${sortColumn} ${sortOrder}

    但更安全的做法是:

    <choose><when test="sortColumn == 'name'">ORDER BY name</when><when test="sortColumn == 'age'">ORDER BY age</when><otherwise>ORDER BY id</otherwise>
    </choose>

4.3 特殊场景处理

LIKE模糊查询的正确写法:

错误方式:

WHERE name LIKE '%${name}%'

正确方式:

// Java代码中处理参数
String nameParam = "%" + name + "%";
WHERE name LIKE #{nameParam}

或使用SQL函数:

WHERE name LIKE CONCAT('%', #{name}, '%')

五、最佳实践建议

5.1 安全性实践

  1. 默认使用#{}:除非确有必要,否则总是使用#{}

  2. 严格过滤${}参数:使用${}时,必须对参数值进行白名单验证

    // 验证表名是否合法
    if (!isValidTableName(tableName)) {throw new IllegalArgumentException("Invalid table name");
    }
  3. 避免用户输入直接用于${}:特别是排序、分组等场景

5.2 性能优化实践

  1. 优先使用#{}:利用预编译语句的缓存优势

  2. 减少${}使用频率:对于频繁调用的SQL,避免使用${}导致无法缓存执行计划

  3. 批量处理动态SQL:对于必须使用${}的场景,考虑批量处理减少SQL解析次数

5.3 代码可维护性实践

  1. 明确注释:在使用${}的地方添加注释说明原因

    <!-- 必须使用${}因为表名是动态的 -->
    SELECT * FROM ${tableName}
  2. 集中管理:将动态部分集中管理,便于维护和安全检查

  3. 单元测试:为使用${}的SQL编写额外的安全测试用例

六、常见问题解答

Q1:为什么ORDER BY不能使用#{}?

A:因为#{}会给参数值添加引号,例如:

ORDER BY 'name' 'DESC'  -- 错误的SQL语法

而正确的应该是:

ORDER BY name DESC

Q2:什么情况下必须使用${}?

A:当SQL语句的非参数部分需要动态变化时,如:

  • 动态表名

  • 动态列名

  • SQL关键字(如ASC/DESC)

Q3:如何安全地使用${}?

A:可以采取以下措施:

  1. 使用白名单验证参数值

  2. 避免直接使用用户输入

  3. 对参数值进行转义处理

  4. 最小化使用范围

总结

#{}和${}在MyBatis中扮演着不同的角色:

  • #{} 是安全的参数占位符,适用于几乎所有参数值的场景,能防止SQL注入,性能更好。

  • ${} 是字符串替换,适用于SQL语句本身需要动态变化的场景,但存在安全风险,应当谨慎使用。

在实际开发中,我们应该遵循以下原则:

  1. 默认使用#{}

  2. 谨慎评估${}的使用必要性

  3. 对必须使用${}的场景实施严格的安全控制

  4. 编写清晰的文档和注释说明使用原因

正确理解和使用这两种占位符,将使你的MyBatis应用更加安全、高效和可维护。

http://www.dtcms.com/a/348100.html

相关文章:

  • Implementing Redis in C++ : E(AVL树详解)
  • spring源码之事务篇(事务管理器整个流程)
  • 笔记 | Anaconda卸载重装
  • Hyperledger Fabric官方中文教程-改进笔记(十五)-从通道中删除组织
  • 【机器学习】3 Generative models for discrete data
  • HTML网页游戏五子棋
  • 电路学习(四)二极管
  • Spring框架相关面试题
  • 【机器学习】4 Gaussian models
  • 【网络运维】Shell 脚本编程:while 循环与 until 循环
  • Python自学笔记11 Numpy的索引和切片
  • Shell脚本-expect
  • VirtualBox安装openEuler24.03
  • 【C++】函数返回方式详解:传值、传引用与传地址
  • 校园跑腿小程序源码 | 跑腿便利店小程序 含搭建教程
  • 如何在 Ubuntu 上安装和配置 Samba ?
  • 2025年渗透测试面试题总结-30(题目+回答)
  • Java 20 新特性及具体应用
  • Cisdem Video Converter for mac 优秀的视频格式转换工具
  • 夜间跌倒检测响应速度↑150%!陌讯多模态骨架追踪算法在智慧养老院的落地实践
  • 埃氏筛|树dfs|差分计数
  • JVM OOM问题排查与解决思路
  • Meta AI 剧变:汪滔挥刀重组,Llama 开源路线告急,超级智能梦碎还是重生?
  • 96、23种设计模式之原型模式(5/23)
  • STM32 USB 之大坑
  • ubuntu中网卡的 IP 及网关配置设置为永久生效
  • Ubuntu24.04环境下causal_conv1d和mamba_ssm安装
  • 嵌入式八股文面试题总结(QT、RTOS、Linux、ARM、C/C++)(持续更新)
  • QT-布局管理器
  • 音视频面试题集锦第 32 期