MyBatis 参数传递详解:从基础到复杂场景全解析
一、参数传递概述:为什么参数处理如此重要?
在 MyBatis 开发中,参数传递是连接 Java 代码与 SQL 语句的桥梁,直接影响 SQL 执行的正确性。不合理的参数传递方式可能导致:
- SQL 语法错误
- 参数值无法正确映射
- 潜在的 SQL 注入风险
- 代码可读性和可维护性下降
MyBatis 提供了灵活多样的参数传递方式,能应对从简单到复杂的各种业务场景。本文将系统讲解 MyBatis 的参数处理机制,涵盖基本类型、对象、集合等多种参数类型,并通过实例展示最佳实践。
二、单个参数传递:最简单的参数处理方式
当 Mapper 接口方法只有一个参数时,MyBatis 的处理方式最为灵活,无需额外配置即可直接使用。
2.1 基本类型参数
Mapper 接口:
java
运行
// 根据ID查询用户
User getUserById(Integer id);
映射文件:
xml
<select id="getUserById" resultType="User">SELECT * FROM user WHERE id = #{id}
</select>
说明:
#{id}
中的名称可以任意,如#{xxx}
也能正常工作- MyBatis 会自动根据参数类型进行类型转换
- 支持的基本类型包括:
int
、long
、String
、boolean
等
2.2 字符串参数与模糊查询
Mapper 接口:
java
运行
// 根据用户名模糊查询
List<User> getUserByUsername(String username);
映射文件:
xml
<select id="getUserByUsername" resultType="User">SELECT * FROM user WHERE username LIKE CONCAT('%', #{username}, '%')
</select>
注意:
- MySQL 中使用
CONCAT()
函数拼接通配符 - Oracle 中可直接使用
'%'||#{username}||'%'
- 避免使用字符串拼接(
${username}
)防止 SQL 注入
2.3 单个对象参数
当参数是一个 Java 对象时,#{}
中需要使用对象的属性名:
Mapper 接口:
java
运行
// 根据用户对象查询(演示用)
User getUserByUser(User user);
映射文件:
xml
<select id="getUserByUser" resultType="User">SELECT * FROM user WHERE username = #{username} AND age = #{age}
</select>
说明:
#{username}
对应 User 对象的getUsername()
方法- 必须保证对象有对应的 Getter 方法
- 参数类型可省略(MyBatis 会自动推断)
三、多个参数传递:@Param 注解的正确使用
当方法有多个参数时,MyBatis 无法直接识别参数名称,需要使用@Param
注解显式指定。
3.1 多个基本类型参数
Mapper 接口:
java
运行
// 多条件查询
List<User> getUserByAgeAndEmail(@Param("minAge") Integer minAge,@Param("maxAge") Integer maxAge,@Param("email") String email
);
映射文件:
xml
<select id="getUserByAgeAndEmail" resultType="User">SELECT * FROM user WHERE age BETWEEN #{minAge} AND #{maxAge}AND email LIKE CONCAT('%', #{email}, '%')
</select>
关键点:
@Param
注解中的值必须与#{}
中的名称一致- 不使用
@Param
时,MyBatis 会使用arg0
、arg1
或param1
、param2
作为参数名 - 推荐始终使用
@Param
,提高代码可读性
3.2 混合参数类型
方法参数可以是基本类型、对象、集合等的混合:
Mapper 接口:
java
运行
// 混合参数查询
List<User> getMixedParams(@Param("user") User user,@Param("status") Integer status,@Param("roles") List<String> roles
);
映射文件:
xml
<select id="getMixedParams" resultType="User">SELECT * FROM user WHERE username = #{user.username}AND status = #{status}AND role IN <foreach collection="roles" item="role" open="(" separator="," close=")">#{role}</foreach>
</select>
说明:
- 引用对象属性时使用
#{对象名.属性名}
格式 - 集合参数可配合
foreach
标签使用
四、特殊参数类型:数组、集合与 Map
4.1 数组参数
Mapper 接口:
java
运行
// 根据ID数组查询
List<User> getUserByIds(@Param("ids") Integer[] ids);
映射文件:
xml
<select id="getUserByIds" resultType="User">SELECT * FROM user WHERE id IN <foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
</select>
4.2 List 集合参数
Mapper 接口:
java
运行
// 根据ID列表查询
List<User> getUserByIdList(@Param("idList") List<Integer> idList);
映射文件:
xml
<select id="getUserByIdList" resultType="User">SELECT * FROM user WHERE id IN <foreach collection="idList" item="id" open="(" separator="," close=")">#{id}</foreach>
</select>
foreach 标签属性说明:
collection
:集合参数名称item
:循环变量名open
:开始符号separator
:分隔符close
:结束符号index
:索引变量名(可选)
4.3 Map 参数
当参数较多且无合适实体类时,可使用 Map 传递参数:
Mapper 接口:
java
运行
// 使用Map查询
List<User> getUserByMap(@Param("params") Map<String, Object> params);
映射文件:
xml
<select id="getUserByMap" resultType="User">SELECT * FROM user <where><if test="params.username != null">AND username LIKE CONCAT('%', #{params.username}, '%')</if><if test="params.age != null">AND age = #{params.age}</if></where>
</select>
使用示例:
java
运行
Map<String, Object> params = new HashMap<>();
params.put("username", "张");
params.put("age", 20);
List<User> users = userMapper.getUserByMap(params);
五、高级参数处理:ResultMap 与复杂对象映射
当数据库表字段与 Java 对象属性名不一致时,需要使用ResultMap
进行自定义映射。
5.1 字段与属性名不一致问题
数据库表字段:user_name
、user_age
Java 对象属性:username
、age
5.2 使用 ResultMap 解决映射问题
映射文件:
xml
<!-- 定义ResultMap -->
<resultMap id="UserResultMap" type="User"><id property="id" column="id"/><result property="username" column="user_name"/><result property="age" column="user_age"/><result property="email" column="email"/>
</resultMap><!-- 使用ResultMap -->
<select id="getUserById" resultMap="UserResultMap">SELECT id, user_name, user_age, email FROM user WHERE id = #{id}
</select>
说明:
id
标签用于映射主键result
标签用于映射普通字段property
:Java 对象属性名column
:数据库表字段名
5.3 关联查询映射
处理一对一、一对多关系时的参数传递:
一对一映射:
xml
<resultMap id="OrderResultMap" type="Order"><id property="id" column="id"/><result property="orderNo" column="order_no"/><!-- 关联用户对象 --><association property="user" javaType="User"><id property="id" column="user_id"/><result property="username" column="username"/></association>
</resultMap>
一对多映射:
xml
<resultMap id="UserOrderResultMap" type="User"><id property="id" column="id"/><result property="username" column="username"/><!-- 关联订单列表 --><collection property="orders" ofType="Order"><id property="id" column="order_id"/><result property="orderNo" column="order_no"/></collection>
</resultMap>
六、参数传递的安全问题:#{} 与 ${} 的区别
MyBatis 提供两种参数占位符:#{}
和${}
,它们的区别直接关系到 SQL 安全。
6.1 #{}:预编译处理
- 会将参数替换为
?
,进行预编译 - 自动处理类型转换
- 防止 SQL 注入
- 适用于大多数参数传递场景
示例:
xml
SELECT * FROM user WHERE username = #{username}
编译后:
sql
SELECT * FROM user WHERE username = ?
6.2 ${}:字符串替换
- 直接替换字符串,不进行预编译
- 存在 SQL 注入风险
- 适用于动态表名、排序字段等场景
示例:
xml
SELECT * FROM ${tableName} ORDER BY ${column} ${order}
替换后:
sql
SELECT * FROM user ORDER BY age DESC
6.3 安全使用 ${} 的场景
- 动态表名:
xml
<select id="getByTable" resultType="User">SELECT * FROM ${tableName} WHERE id = #{id}
</select>
- 动态排序:
xml
<select id="getAllOrderBy" resultType="User">SELECT * FROM user ORDER BY ${column} ${direction}
</select>
安全建议:
- 对
${}
参数进行白名单校验 - 避免直接使用用户输入作为
${}
参数 - 优先使用
#{}
,仅在必要时使用${}
七、参数传递最佳实践
保持参数数量精简:
- 超过 3 个参数时,考虑封装为实体类
- 零散参数较多时,使用 Map 或 DTO 对象
始终使用 @Param 注解:
- 提高代码可读性
- 避免参数顺序变化导致的错误
合理使用 ResultMap:
- 字段名与属性名不一致时必须使用
- 关联查询时显式定义映射关系
防止 SQL 注入:
- 优先使用
#{}
- 对
${}
参数进行严格校验
- 优先使用
复杂参数的文档说明:
- 对 Map 参数和复杂对象添加注释
- 说明每个参数的含义和用途
八、常见问题与解决方案
Parameter 'xxx' not found:
- 检查是否缺少
@Param
注解 - 确保
@Param
值与#{}
中的名称一致
- 检查是否缺少
无效的参数类型转换:
- 检查参数类型与数据库字段类型是否匹配
- 考虑使用类型处理器(TypeHandler)
关联查询结果为 null:
- 检查
ResultMap
中的关联配置 - 确保 JOIN 查询的关联条件正确
- 检查
SQL 注入漏洞:
- 将
${}
替换为#{}
- 对动态参数进行白名单验证
- 将
总结
参数传递是 MyBatis 开发中的基础而重要的环节,本文详细介绍了各种参数类型的传递方式,包括基本类型、对象、集合等,以及ResultMap
的使用和 SQL 安全问题。掌握这些知识,能够帮助你处理各种复杂的参数场景,编写安全、高效的 MyBatis 代码。在实际开发中,应根据具体业务场景选择合适的参数传递方式,并始终注意 SQL 安全问题。