← 返回题目列表

MyBatis 多参数、对象和集合参数是如何绑定的?

高频 中等 第 6 / 24 题 更新于 2026/07/26
MyBatisParam参数绑定OGNL

简化版

MyBatis 会把 Mapper 方法参数转换成一个供 OGNL 和 #{} 使用的参数对象;单个 JavaBean 可直接按属性访问,多参数建议用 @Param 显式命名。单个 List、Collection 或数组会提供相应集合键,批量 SQL 应用 <foreach> 遍历,不能依赖不同编译配置下不稳定的真实参数名。

详细版

一个复杂对象参数可写 #{user.name} 或直接按根对象属性写 #{name};多个普通参数会经 ParamNameResolver 组成 Map,@Param("id") 提供稳定名称,同时 MyBatis 还可能提供 param1param2 等通用名。未标注时能否使用源码参数名取决于 useActualParamName 和是否保留 -parameters 元数据。

单个 Collection 参数可用 collection,List 还可用 list,数组可用 array;若加 @Param("ids"),应使用显式名称 ids。RowBounds、ResultHandler 等特殊参数不会作为普通 SQL 参数参与命名。

完整版教学

一、单个对象参数

User find(UserQuery query);
SELECT * FROM users
WHERE status = #{status}
  AND created_at >= #{startTime}

query 本身就是根参数对象,MyBatis 通过 MetaObject/属性访问找到 getter 或字段。嵌套对象可以写 #{address.city},但动态条件要先判断中间对象是否为 null。

Mapper 形态XML 中推荐名字稳定性注意点
单个 JavaBean#{name}#{query.name} 视包装而定较稳定嵌套属性要防 null
多个普通参数@Param 指定的名字最稳定不依赖编译参数名
单个 Listlist 或显式 @Param("ids")显式注解更稳定foreach 展开
单个数组array 或显式名称显式注解更稳定foreach 展开
Map 参数Map key灵活但弱类型键拼错运行期才发现

记忆钩子:Mapper 参数进入 MyBatis 后都会变成“一个可被 OGNL 读取的参数对象”;多参数时,@Param 就是在给这个对象的 key 固定名字。

二、多个参数为什么建议 @Param

User find(@Param("id") long id,
          @Param("status") String status);

XML 可稳定使用 #{id}#{status}。不加注解时,MyBatis 可能使用实际参数名,也会提供通用名称,但实际参数名是否存在受编译器 -parameters 影响;重构参数名也可能悄悄改变 XML 契约。

@Param 把 Mapper API 与 SQL 映射的名字显式固定,更适合公共方法和多人维护项目。

方法: find(long id, String status)

不加 @Param 时可能可用:
  #{arg0}, #{arg1}
  #{param1}, #{param2}
  若保留 -parameters,也可能有 #{id}, #{status}

加 @Param 后稳定可用:
  #{id}, #{status}
  同时仍可能有 param1/param2

假设某团队本地编译保留了 -parameters,线上构建没保留,XML 里写 #{status} 就可能本地可用、线上失败。@Param 的价值就在于把这个契约从“编译器是否保留元数据”变成“源码显式声明”。

三、集合参数如何展开

List<User> findByIds(@Param("ids") List<Long> ids);
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
  #{id}
</foreach>

foreach 为每个元素生成独立参数映射。空集合可能生成非法 IN () 或没有条件,调用前或动态 SQL 中必须明确“空列表返回空结果还是忽略条件”,不能任由它改变查询范围。

四、Map 参数的取舍

Map 可以灵活传任意键,但缺少编译期属性检查,键拼错只能运行时报错。固定业务查询优先定义 Query DTO;只有真正动态、字段集合不固定的框架层场景才使用 Map。

返回 Map 与参数 Map 也不要混淆,前者常由 @MapKey 指定结果键,后者只是输入容器。

五、OGNL 与动态条件

<if test="name != null and name != ''"> 使用 OGNL 读取参数。OGNL 表达式负责是否拼接 SQL,#{} 负责 JDBC 值绑定,两者处在不同阶段。

条件过于复杂时应在 Java 中先计算明确字段,避免把大量业务规则塞进 XML 表达式。对字符串、数字和枚举的比较也要注意类型,不要用偶然的隐式转换。

六、特殊参数和空集合边界

RowBoundsResultHandler 这类特殊参数通常不参与普通 SQL 参数命名,它们影响 MyBatis 执行行为,而不是作为 #{} 值绑定。面试中如果被问“为什么我第 2 个参数不是 param2”,要想到特殊参数会被跳过。

集合参数还要处理空集合。比如 ids = [] 时,WHERE id IN () 在很多数据库是非法 SQL;如果你用动态 SQL 把整个 IN 条件去掉,可能从“查不到任何用户”变成“查全部用户”。因此空集合最好在 Service 层直接返回空列表,或者在 SQL 中显式生成不成立条件,如 1 = 0

七、常见误区与追问

  • 误区:多参数可以长期依赖真实参数名。 真实参数名依赖 useActualParamName 和编译元数据,公共 Mapper 方法应使用 @Param 固定名称。
  • 误区:param1param2@Param 更推荐。 通用名可用但可读性差,参数顺序变更时也容易误绑定。
  • 追问:单个 List 为什么常见 list 这个名字? MyBatis 会为单个 List/Collection/数组提供默认集合键,但显式 @Param("ids") 更清晰。
  • 追问:foreach 空集合怎么处理? 必须定义业务语义,通常在 Service 直接返回空结果,或生成不成立条件,不能让条件静默消失。
  • 误区:Map 参数最灵活所以最适合业务查询。 Map 没有编译期字段检查,固定查询更推荐 Query DTO。
  • 追问:OGNL 和 #{} 分别负责什么? OGNL 在动态 SQL 阶段判断和取值,#{} 在 JDBC 阶段生成占位符并绑定参数。

八、加强记忆

单对象按属性取,多参数用 @Param 固定名字,集合用 foreach 展开。实际参数名受编译元数据影响,param1 等通用名可用但可读性差;参数契约越明确,XML 与 Mapper 重构越安全。