{}通过预编译防止SQL注入并提升性能,${}则直接字符串替换易引发安全风险;前者用于参数值,后者仅用于表名列名等需动态拼接的场景且必须严格校验。

MyBatis里,#{} 和 ${} 的核心区别在于它们处理参数的方式:#{} 会将参数视为一个值,通过预编译(PreparedStatement)的方式安全地进行参数替换,有效防止SQL注入;而 ${} 则是直接的字符串替换,它会将传入的内容原样拼接到SQL语句中,虽然在某些特定场景下有用,但极易引发SQL注入风险。
当你使用 #{} 语法时,MyBatis 会在底层为你的SQL语句生成一个 PreparedStatement 对象。想象一下,数据库接收到的查询语句中,参数的位置都是用问号 ? 占位的。比如,你写 SELECT * FROM users WHERE id = #{userId},MyBatis 会把它翻译成类似 SELECT * FROM users WHERE id = ?,然后把 userId 的值作为参数绑定到这个问号上。这种方式的好处是显而易见的:数据库在执行之前就已经确定了SQL语句的结构,它只关心 ? 后面跟着的是什么值,而不会把这个值当作SQL命令的一部分来解析。这就从根本上杜绝了恶意的SQL代码片段被注入执行的可能性。而且,数据库通常会对预编译的SQL语句进行优化,缓存执行计划,这对性能也是有益的。
而 ${} 就完全是另一回事了。它就像一个简单的文本替换工具。你写 SELECT * FROM ${tableName} WHERE id = #{userId},如果 tableName 的值是 users,那最终执行的SQL就是 SELECT * FROM users WHERE id = ?。看起来好像没什么问题?但如果 tableName 的值是 users; DROP TABLE orders; 呢?那最终的SQL就会变成 SELECT * FROM users; DROP TABLE orders; WHERE id = ?。这下麻烦就大了,数据库可能会先执行 SELECT,然后紧接着就把你的 orders 表给删了。这就是典型的SQL注入。所以,除非你对传入的字符串有绝对的控制和严格的校验,否则使用 ${} 是非常危险的。它主要用于那些不能被参数化的SQL元素,比如表名、列名、排序字段(ORDER BY 后面的字段)等等。
绝大多数情况下,只要你传递的是一个数据值,一个需要被查询、插入、更新或删除的具体内容,你就应该无脑选择 #{}。这不仅仅是出于安全的考虑,更是MyBatis设计哲学的一部分。它帮你处理了数据类型转换、特殊字符转义等一系列繁琐的事情,让你能专注于业务逻辑本身。
举个例子,无论是用户的ID、姓名、年龄,还是商品的库存、价格,订单的状态,这些都是数据。当你需要根据这些数据进行筛选、更新或者插入时,#{} 就是你的不二之选。比如:
<select id="getUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
<insert id="insertUser">
INSERT INTO user (name, age) VALUES (#{name}, #{age})
</insert>
<update id="updateUserAge">
UPDATE user SET age = #{age} WHERE id = #{id}
</update>你看,这里的 id、name、age 都是具体的值。使用 #{},MyBatis 会自动帮你处理好它们的数据类型,比如 id 是整数,name 是字符串,它会以最安全的方式把它们传递给数据库。你几乎不需要担心什么,只管把参数传进去就行。这是一种规范,也是一种最佳实践,能让你的代码更健壮、更易于维护。
既然 #{} 这么好,那为什么还需要 ${} 呢?这就是一个“不得不为之”的选择。有些SQL语句的组成部分,它本身就不是一个“值”,而是一个SQL关键字或者一个数据库对象的名字。比如,你想动态地指定查询的列名,或者动态地指定排序的字段和顺序。
考虑一个场景,你有一个通用的查询接口,用户可以指定按哪个字段排序,是升序还是降序。这时候,ORDER BY 后面的列名和 ASC/DESC 就不能用 #{} 了,因为它们不是数据,而是SQL语句的结构。
<!-- 错误示例,不能用 #{} -->
<!-- <select id="getUsersSorted" resultType="User">
SELECT * FROM user ORDER BY #{columnName} #{sortOrder}
</select> -->
<!-- 正确但有风险的示例 -->
<select id="getUsersSorted" resultType="User">
SELECT * FROM user ORDER BY ${columnName} ${sortOrder}
</select>在这种情况下,columnName 和 sortOrder 必须用 ${} 来替换。因为数据库需要直接看到 ORDER BY name ASC 这样的完整字符串,而不是 ORDER BY ? ?。
但是,这里面的风险是巨大的,我前面也提到了。如果 columnName 传入的是 name; DROP TABLE user; 这样的恶意字符串,那么你的用户表可能就没了。
要规避这种风险,通常的做法是:
${} 参数。比如,如果 columnName 只能是 id, name, age 这三个之一,你就写个 if/else 或者 switch 来判断,只允许这些合法值通过。${} 替换的参数,而是提供下拉框、单选按钮等方式,让他们选择预设好的合法值。说实话,每次用到 ${} 我都会心里咯噔一下,然后立刻去检查有没有做严格的输入校验。这就像在代码里埋了个小地雷,虽然你知道它在哪儿,但总得小心翼翼地绕过去。
除了安全性这个最核心的区别,#{} 和 ${} 在性能上确实也存在差异,这主要与数据库如何处理SQL语句有关。
当数据库收到一个SQL查询时,它通常会经历几个阶段:解析(parsing)、优化(optimization)、执行(execution)。
使用 #{} 时,MyBatis 会生成 PreparedStatement。这意味着SQL语句的结构是固定的,参数是独立的。数据库第一次收到这样的语句时,会对其进行解析和优化,生成一个执行计划(execution plan),然后将这个计划缓存起来。后续再执行相同的SQL语句(只是参数不同),数据库可以直接复用之前缓存的执行计划,省去了重复解析和优化的开销。这对于高并发、频繁执行的查询来说,能显著提升性能。数据库只需要把新的参数绑定到已经准备好的执行计划上就行了,效率自然更高。
而 ${} 则不同。每次使用 ${} 拼接的SQL语句,对于数据库来说,都是一条全新的、从未见过的SQL语句。即使只有一小部分内容不同,数据库也认为这是一条全新的查询。因此,每次执行这样的SQL语句,数据库都需要重新进行解析、优化,并生成新的执行计划。这个过程虽然在单次查询中开销可能不明显,但在高并发或大量重复查询的场景下,这些额外的解析和优化开销就会累积起来,对数据库的CPU和内存造成不必要的负担,从而影响整体性能。
所以,从性能角度看,#{} 的预编译和参数绑定机制,使得数据库能够更高效地处理重复查询,减少了不必要的解析和优化开销,在多数情况下都优于 ${}。${} 带来的性能损耗,虽然可能不如SQL注入的风险那么立竿见影,但长期来看,对系统稳定性和扩展性也是一个潜在的隐患。
以上就是mybatis 中#和$4}的区别是什么?的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号