一、MyBatis参数传递的重要性

在持久层框架的使用过程中,参数传递犹如快递包装中的填充物——方式选择直接影响"数据包裹"能否安全到达SQL目的地。MyBatis作为半自动化的ORM框架,其参数处理机制既保留了灵活性又存在使用门槛,特别是在处理复杂参数场景时,开发者经常会遇到"明明参数传了值怎么报空指针"的灵魂拷问。

技术栈声明:本文使用MyBatis 3.5.9 + MySQL 8.0演示,JDK版本为11

二、单个参数的传递艺术

2.1 基本类型参数的优雅传递

<!-- 通过接口参数名直接引用 -->
<select id="selectUser" resultType="User">
    SELECT * FROM users WHERE user_id = #{userId}
</select>

<!-- 没有在接口中定义参数名称时(需开启-parameters编译参数) -->
<select id="selectUser" resultType="User">
    SELECT * FROM users WHERE user_id = #{arg0}
</select>

使用技巧

  • 建议为接口方法参数添加@Param注解增强可读性
  • 基本类型参数的判空处理应特别注意:#{age,jdbcType=INTEGER}

2.2 对象参数的深度访问

public class UserQuery {
    private String username;
    private Integer status;
    // 省略getter/setter
}

// Mapper接口
User selectByCondition(UserQuery query);
<select id="selectByCondition" resultType="User">
    SELECT * FROM users 
    WHERE username LIKE CONCAT('%',#{username},'%')
    AND status = #{status}
</select>

嵌套属性访问示例

<!-- 查询部门用户时 -->
<select id="selectDeptUsers" resultType="User">
    SELECT * FROM users 
    WHERE dept_id = #{dept.deptId}
    AND create_time > #{query.startTime}
</select>

2.3 Map参数的灵活运用

// Mapper接口
List<User> searchUsers(Map<String, Object> params);
<select id="searchUsers" resultType="User">
    SELECT * FROM users 
    WHERE 
    <if test="minAge != null">age >= #{minAge}</if>
    <if test="maxAge != null">AND age <= #{maxAge}</if>
    <if test="roleList != null">
        AND role_id IN 
        <foreach collection="roleList" item="role" open="(" separator="," close=")">
            #{role}
        </foreach>
    </if>
</select>

特征验证:参数值为null时MyBatis的特殊处理机制

三、多个参数的组合拳法

3.1 @Param注解的魔法效应

// Mapper接口
List<User> selectByPage(@Param("offset") int offset, 
                        @Param("size") int size,
                        @Param("orderBy") String orderBy);
<select id="selectByPage" resultType="User">
    SELECT * FROM users 
    ORDER BY ${orderBy}
    LIMIT #{offset}, #{size}
</select>

关键区别

  • #{}${}的SQL注入防护差异
  • 分页参数的类型转换隐患

3.2 隐式Map参数的妙用

<select id="selectComplex" resultType="User">
    SELECT * FROM users 
    WHERE username = #{param1}
    AND create_time BETWEEN #{param2.start} AND #{param2.end}
</select>

参数定位规则

  • param1/param2...的自然索引
  • arg0/arg1...的编译参数

3.3 参数对象封装技法

// 分页查询参数包装对象
public class PageQuery {
    private int current;
    private int size;
    private String keyword;
    // 计算offset的getter方法
    public int getOffset() {
        return (current - 1) * size;
    }
}
<select id="search" resultType="User">
    SELECT * FROM users 
    WHERE username LIKE CONCAT('%',#{keyword},'%')
    LIMIT #{offset}, #{size}
</select>

四、集合参数的进阶处理

4.1 List参数的遍历艺术

<update id="batchUpdateStatus">
    UPDATE users SET status = #{status} 
    WHERE user_id IN 
    <foreach collection="userIds" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</update>

参数验证:foreach元素的必要属性检查清单

4.2 数组参数的奇幻漂流

// Mapper接口
int deleteByIds(Long[] ids);
<delete id="deleteByIds">
    DELETE FROM users 
    WHERE user_id IN 
    <foreach collection="array" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</delete>

别名秘密

  • array → 数组参数
  • list → List参数
  • collection → 自定义参数名

4.3 Map包装的集合参数

// Mapper接口
int batchInsert(@Param("userList") List<User> users);
<insert id="batchInsert">
    INSERT INTO users (username, email) VALUES 
    <foreach collection="userList" item="user" separator=",">
        (#{user.username}, #{user.email})
    </foreach>
</insert>

4.4 嵌套集合的特殊处理

// 部门-角色关联参数
public class DeptRoleParam {
    private Long deptId;
    private List<Integer> roleIds;
}
<insert id="insertDeptRoles">
    INSERT INTO dept_role (dept_id, role_id) VALUES 
    <foreach collection="roleIds" item="roleId" separator=",">
        (#{deptId}, #{roleId})
    </foreach>
</insert>

五、关联技术深入:动态SQL

5.1 条件分支的优雅写法

<select id="dynamicSearch" resultType="User">
    SELECT * FROM users 
    <where>
        <choose>
            <when test="username != null">
                username = #{username}
            </when>
            <when test="email != null">
                email LIKE CONCAT(#{email},'%')
            </when>
            <otherwise>
                status = 1
            </otherwise>
        </choose>
    </where>
</select>

5.2 SQL片段复用技术

<sql id="userBaseColumns">
    user_id, username, email, create_time
</sql>

<select id="selectUserList" resultType="User">
    SELECT 
    <include refid="userBaseColumns"/>,
    department_name 
    FROM users u 
    JOIN departments d ON u.dept_id = d.dept_id
</select>

六、应用场景全景分析

  1. Web分页查询:封装分页参数对象
  2. 复杂条件搜索:Map参数配合动态SQL
  3. 批量数据操作:foreach元素的批量处理
  4. 多表关联查询:参数对象的多级嵌套
  5. 业务状态流转:带条件的参数更新

七、技术优缺点对比

优势维度

  • 参数处理方式的多样性:支持7种参数传递模式
  • 类型转换的灵活性:自动处理基本类型与JDBC类型映射
  • 动态SQL的配合能力:与OGNL表达式无缝集成

注意事项

  1. 参数名为空时的定位困惑
  2. 集合参数的类型转换陷阱
  3. Map参数的类型安全危机
  4. 批量操作的性能天花板

八、参数传递的黄金法则

  1. 永远进行参数非空校验
  2. 优先使用参数对象封装
  3. 警惕${}的SQL注入风险
  4. 合理设置jdbcType类型
  5. 批量操作采用分批提交

九、避坑指南

  1. 隐式参数名的误会:未开启-parameters编译参数时不要依赖参数名
  2. 泛型擦除的陷阱
<!-- 错误写法 -->
<foreach collection="list" item="item">...</foreach>

<!-- 正确写法 -->
<foreach collection="userList" item="user">...</foreach>
  1. 类型处理器的选择:自定义TypeHandler的注册问题

十、总结与展望

经过对MyBatis参数传递机制的全方位解析,我们可以清晰看到:

  • 单个参数的简约性 VS 多参数的组合型
  • 基础类型的直接性 VS 对象结构的组织性
  • 集合操作的灵活性 VS 性能优化的必要性

随着MyBatis 3.5版本对参数处理的持续优化,参数验证机制和类型推导能力的增强,未来的参数传递将会更加智能和安全。开发者在掌握现有技术的同时,也要持续关注Param注解的增强特性等新变化。