前言
在 MyBatis 开发中,#{} 和 ${} 是参数绑定的两种核心语法,看似都能传递参数,底层原理、安全性、使用场景天差地别。
新手最容易混淆两者,导致 SQL 注入、参数报错、SQL 语法异常等问题。本文从底层原理、语法区别、安全性、使用场景、实战示例全方位讲解,帮你彻底吃透。
一、核心结论(先记牢)
| 对比项 | #{} | ${} |
|---|---|---|
| 底层实现 | 预编译 SQL,使用 ? 占位符 | 直接字符串拼接,不做预处理 |
| SQL 注入 | 安全,可防注入 | 不安全,无法防注入 |
| 参数类型 | 自动识别类型(字符串自动加引号) | 纯文本拼接,不处理类型 |
| 使用场景 | 90% 场景:查询条件、插入值、更新值 | 表名 / 列名动态传入、排序字段、SQL 片段 |
| 性能 | 高,支持 SQL 缓存 | 低,每次都编译新 SQL |
一句话总结:
能用 #{} 绝对不用 ${};只有动态传入表名 / 列名 / 排序关键字时,才被迫用 ${}。
二、底层原理解析
3.1 #{ }:预编译模式(PreparedStatement)
MyBatis 会将 #{} 替换为 JDBC 预编译的 ? 占位符。
示例:
<select id="selectById" resultType="User">select * from user where id = #{id}
</select>
最终执行的 SQL:
sql
select * from user where id = ?
-
参数会安全赋值,自动处理数据类型
-
字符串会自动加单引号 ' '
-
数据库只编译一次 SQL,多次复用
3.2 ${ }:字符串直接拼接
MyBatis 会把 ${} 里的参数直接拼接到 SQL 中,不做任何处理。
示例:
<select id="selectById" resultType="User">select * from user where id = ${id}
</select>
传入 id=1,最终 SQL:
select * from user where id = 1
三、最关键区别:安全性(SQL 注入)
3.1 #{ } 防 SQL 注入
假设查询语句:
select * from user where username = #{username}
恶意传入:' or '1'='1
#{} 处理后:
select * from user where username = ?
参数被当作普通字符串,无法破坏 SQL 结构 ✅
3.2 ${ } 存在严重注入风险
同样语句:
select * from user where username = ${username}
恶意传入:' or '1'='1
拼接后 SQL:
select * from user where username = '' or '1'='1'
直接查询全表数据,数据泄露! ❌
四、字符串参数的巨大区别
4.1 使用 #{}(正确)
select * from user where username = #{username}
传入:张三
执行 SQL:
select * from user where username = '张三'
✅ 自动加单引号,不会报错
3.2 使用 ${}(必须手动加引号)
select * from user where username = '${username}'
否则会报SQL语法错误
五、必须使用 ${ } 的场景(重点)
#{} 无法替换 表名、列名、排序关键字、SQL 关键字,这些场景必须用 ${}。
场景 1:动态表名
调用:tableName = "user"执行:select * from user where id=?
场景 2:动态排序字段 / 排序方式
<select id="selectList" resultType="User">select * from userorder by ${orderBy} ${sortType}
</select>
传入:orderBy="create_time", sortType="desc"执行:order by create_time desc
场景 3:动态列名查询
select ${columnName} from user
六、实战开发规范(必看)
-
查询条件、插入值、更新字段值 → 一律用 #{}
-
动态表名、动态列名、动态排序 → 必须用 ${}
-
使用 ${} 必须做参数校验,防止 SQL 注入
-
禁止用户可控的参数直接使用 ${}(如前端传入的表名字段)
七、总结
-
#{} 是预编译,安全,自动处理类型和引号,适合 90% 场景
-
${} 是字符串拼接,不安全,不处理引号,只适合动态表名 / 列名
-
优先使用 #{},不得已才用 ${}
-
使用 ${} 必须做好安全校验
