MyBatis 参数处理
SQL 里怎么引用 Java 传进来的参数,是新手最容易踩坑的地方。MyBatis 提供两种占位方式:#{}(预编译占位,安全)与 ${}(字符串拼接,有注入风险)。先搞清楚参数命名规则,再分清两者的差别。
单参数:引用随意
只有一个参数时,占位符里的名字无关紧要,只要不冲突:
User u = session.selectOne("...UserMapper.selectById", 1);
<!-- 单个简单类型参数:名字随便写(常用 id、value) -->
<select id="selectById" resultType="user">
SELECT id, name, age FROM user WHERE id = #{id}
</select>
参数是一个对象时,#{} 里写它的属性名:
<update id="update">
UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}
</update>
#{name} 等价于调用 user.getName();传入 Map 时则按 key 取值。
多参数:必须用 @Param 起名字
接口方法有多个参数时,MyBatis 不知道 #{name} 该取谁,必须用 @Param 命名:
import org.apache.ibatis.annotations.Param;
User selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);
<select id="selectByNameAndAge" resultType="user">
SELECT id, name, age FROM user
WHERE name = #{name} AND age = #{age}
</select>
不加 @Param 也能跑,但只能用默认名 #{arg0}、#{arg1} 或 #{param1}、#{param2}(二者等价,以官方文档为准),可读性差、重命名参数容易出错,所以多参数一律推荐 @Param。若编译时开启了 -parameters,也可能直接用真实参数名,具体以官方文档为准。
#{}:预编译占位(首选)
#{} 会被 MyBatis 渲染成 JDBC PreparedStatement 的占位符 ?,参数值由驱动单独传入:
-- 你写的 SQL
WHERE name = #{name}
-- 实际执行的 SQL(参数通过 setString 传入,不属于 SQL 文本)
WHERE name = ?
- 天然防 SQL 注入:用户输入永远只是"值",不会成为 SQL 语句的一部分。
- 可以附加类型信息:
#{age,jdbcType=INTEGER}、#{createTime,typeHandler=...}(typeHandler 细节以官方文档为准)。 - 适合所有"值"场景:where 条件、insert 值、update 值。
${}:字符串替换(慎用)
${} 不做预编译,直接把值拼接进 SQL 文本:
// 用户输入:"' OR '1'='1"
String keyword = "' OR '1'='1";
<!-- 危险写法 -->
<select id="find" resultType="user">
SELECT * FROM user WHERE name = '${name}'
</select>
拼出来变成 WHERE name = '' OR '1'='1',条件恒真,整表数据被拖走——这就是典型的 SQL 注入。官方文档(String Substitution 一节)明确提醒:${} 拼接的内容不要来自不可信输入。
${} 唯一合理的用途是"SQL 结构"本身需要动态化,比如表名、列名、排序字段(这些地方 #{} 会因带引号而失效):
<select id="selectOrdered" resultType="user">
SELECT id, name, age FROM user ORDER BY ${orderColumn}
</select>
即便此时也必须做白名单校验,例如只允许传入预定义的列名集合。
对比小结
| 对比项 | #{} | ${} |
|---|---|---|
| 生成方式 | PreparedStatement 占位符 ? | 直接拼接字符串 |
| 是否防注入 | 是 | 否,有注入风险 |
| 适用场景 | 值:条件、插入、更新 | 结构:表名、列名、ORDER BY |
| 使用建议 | 默认全用 | 极少用,且必须白名单 |
小结
传参三板斧:单参数随便写、对象写属性、多参数用 @Param。占位符优先 #{},只有动态表名/列名这类 SQL 结构才碰 ${},并加上白名单校验。