在MyBatis中,#{}和${}都是用来处理SQL参数替换的占位符,但它们的底层机制和安全影响截然不同。#{}采用预编译参数化查询,能有效防止SQL注入,而${}是直接的字符串拼接,存在安全风险,通常用于动态表名、列名等非参数场景。简单说,用#{}处理用户输入,用${}处理SQL结构部分。

#{}的工作原理:预编译与安全防护

#{}是MyBatis默认推荐的参数替换方式。它的核心原理是预编译(PreparedStatement)。当MyBatis解析SQL时,遇到#{}会将其转换为一个占位符(如?),然后将参数值通过JDBC驱动安全地传递给数据库。数据库会先编译SQL结构,再将参数值作为独立数据处理,因此用户输入即使包含恶意SQL片段,也会被当作普通字符串,无法改变原SQL逻辑。例如:

SELECT * FROM users WHERE username = #{name}

实际执行时,SQL被编译为"SELECT * FROM users WHERE username = ?",参数"name"的值会被安全绑定。这种方式不仅防止了SQL注入,还提升了性能,因为同一SQL结构可被数据库缓存和复用。

${}的工作原理:字符串拼接与风险

${}实现的是简单的字符串替换。MyBatis会将${}内的内容直接拼接到SQL语句中,生成完整的SQL字符串再提交给数据库。这相当于传统的Statement执行方式,没有任何过滤或转义。例如:

SELECT * FROM users WHERE username = '${name}'

如果用户输入是"admin' OR '1'='1",拼接后的SQL会变成"SELECT * FROM users WHERE username = 'admin' OR '1'='1'",导致条件永远为真,数据全部泄露。因此,${}绝不能用于处理用户输入参数。

核心区别对比:安全、性能与使用场景

安全性是两者最根本的区别。#{}从设计上杜绝了SQL注入,而${}需要开发者自行承担风险。在性能上,#{}的预编译特性有利于数据库优化执行计划,尤其在高频查询中表现更佳;${}每次可能生成不同SQL语句,无法利用缓存。但${}在动态SQL片段处理上有其不可替代性,比如动态指定表名、列名或排序字段:

SELECT * FROM ${tableName} ORDER BY ${orderColumn}

这种情况下,表名和列名是SQL结构的一部分,无法使用预编译占位符。但必须确保这些值来自可信来源(如系统配置,而非用户输入)。

常见误区与深度解析

有些开发者误认为${}在模糊查询中更方便,例如WHERE username LIKE '%${keyword}%'。但这非常危险。正确做法是使用#{}并结合SQL函数:LIKE CONCAT('%', #{keyword}, '%')。另一种误区是在IN语句中使用${},如WHERE id IN (${ids})。这同样危险,应使用MyBatis的动态SQL标签<foreach>配合#{}来安全遍历集合。

从底层看,#{}会对参数进行适当的类型处理(如字符串加引号),而${}是“原样输出”。如果${}替换的是数值,可以不加引号,但这需要精确控制数据类型。在复杂SQL中,混合使用时必须保持清醒:任何用户可控数据必须进入#{}。

高级场景:何时必须使用${}

除了动态表名/列名,一些高级场景也依赖${}。例如,需要动态指定数据库模式(schema)、或在使用某些数据库特定函数和关键字时(如调用Oracle的TO_DATE函数,其格式参数需直接嵌入SQL)。另一个场景是分库分表策略中,根据算法动态计算表名后缀。但这些操作都应在服务层严格校验,禁止用户输入直接参与拼接。

最佳实践与安全规范

1. 默认使用#{}原则:所有传入SQL的参数,只要不是SQL关键字或结构部分,一律使用#{}。
2. 限制${}的使用范围:建立代码审查规范,对${}的使用进行标记和重点审计,确保其参数值内不含任何用户输入。
3. 输入过滤与白名单:对于必须使用${}的动态部分,采用白名单机制。例如,排序字段只允许“create_time”、“id”等预定义的列名。
4. 利用MyBatis特性:对于动态SQL,优先使用<if>、<choose>、<foreach>等标签,而非字符串拼接。
5. 日志监控:在生产环境日志中,监控最终执行的SQL语句,及时发现异常的拼接行为。

总结:根据本质选择工具

#{}和${}的区别本质上是“参数”与“代码”的区别。#{}传递的是数据值,${}拼接的是SQL代码片段。在软件开发中,永远不要将用户输入当作代码执行。理解这一原则,就能自然做出正确选择:用#{}守卫数据安全,用${}处理可控的结构动态性,两者结合构建既灵活又坚固的数据访问层。