933 字
约 3 分钟
8
S08-B02 · 参数与结果映射:`#{}` vs `${}`

适用读者:所有写 @Select 的人——这一篇能防住一类真实漏洞。
前置:S08-B01。
实测环境:MyBatis 3.0.3 + H2(2026-11-05)。
覆盖旧文:896(59) 898(77) + 空壳 1137 补写 = 136 点。
覆盖扩展:E-S08-003(#{} 预编译 vs ${} 拼接)、E-S08-010(空壳 1137 SQL 预编译,已补写)。
上一篇:S08-B01 · Mapper 为什么不用写实现类
下一篇:S08-B03 · 动态 SQL。


1. 一句话结论

#{} 走 JDBC 预编译占位符(?),${} 是字符串直接拼接进 SQL。前者安全,后者一旦接外部输入就是注入漏洞。


2. 实测:注入串的两种结果

@Select("select * from sys_user where user_name = '${name}'")   // 拼接
List<User> byNameUnsafe(@Param("name") String name);

@Select("select * from sys_user where user_name = #{name}")     // 预编译
List<User> byNameSafe(@Param("name") String name);

正常输入 alice:

2. #{} 传 name=alice → [User(1,alice,20)]
3. ${} 传 name=alice → [User(1,alice,20)]

注入串 x' OR '1'='1:

4. ${} 传注入串 → 条数 3          ← 返回全表!
5. #{} 传注入串 → 条数 0          ← 安全
写法 拼出的 SQL 结果
${} ... where user_name = 'x' OR '1'='1' 3 行(全表泄露)
#{} ... where user_name = ?(参数 x' OR '1'='1) 0 行

这是本篇最重要的一段:不是"可能被注入",是一个字符串就把全表查出来了。


3. ${} 什么时候必须用

场景 说明
动态表名/列名 order by ${column}、from ${table}
排序方向 order by ${dir}(只允许 asc/desc)
已知枚举拼接 数量固定的分类条件

使用纪律:${} 的内容必须来自"白名单",绝不能来自用户输入。

// 正确:白名单校验后再拼接
String col = ALLOWED.contains(column) ? column : "id";

4. 结果映射:驼峰与 ResultMap

实测:user_name 列自动映射到 userName 字段(配置 map-underscore-to-camel-case=true 后):

2. #{} 传 name=alice → [User(1,alice,20)]
方式 写法 适用
自动映射 列名 snake_case ↔ 字段 camelCase 约定一致时
@Results / XML resultMap 显式映射 列名与字段不同、需要类型转换
别名 select user_name as userName 简单场景

判据:"列名和字段名能对上吗?" 能 → 开驼峰自动映射;不能 → 显式 ResultMap。


5. 参数传递的四种写法

写法 说明
单个基本类型 #{任意名} 都行(只有一个参数)
@Param 多参数必须用
POJO #{fieldName}
Map #{key}

坑:多参数不加 @Param,MyBatis 会用 arg0/param1 命名→ #{name} 报 BindingException。


6. 五个误区

误区 真相
"${} 只是性能更好" 是拼接,可注入
"参数化查询不会被注入" 只有 #{} 才是参数化
"驼峰映射默认开启" 需配置(Spring Boot 下可 mybatis.configuration.map-underscore-to-camel-case)
"多参数不用 @Param" 会 BindingException
"${} 完全不能用" 动态表名/排序列需要,但要白名单

7. 自检

  1. #{} 与 ${} 在 JDBC 层分别变成什么?
  2. 实测里注入串在 ${} 下返回几行?为什么?
  3. ${} 的合法用途有哪些?纪律是什么?
  4. 驼峰映射怎么开?
  5. 多参数不加 @Param 会怎样?

8. 下一篇

S08-B03 · 动态 SQL:<if>/<where>/<foreach>

注讲座完,下一篇讲"条件拼接"的正确做法——用标签而不是字符串拼。


本篇属于系列《S08 · MyBatis 持久层》。旧文来源:A-896/898(空壳 1137 补写);扩展点 E-S08-003。注入对照为 MyBatis 3.0.3 + H2 实测。

S08-B02 · 参数与结果映射:`#{}` vs `${}`
https://www.clxhxhhr.top/posts/5283/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。