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 结构才碰 ${},并加上白名单校验。

笔记加载中…