乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

@Param

导学

本节学习目标:

  • 掌握 @Param 注解的基本用法,能够为 Mapper 接口方法的参数显式命名
  • 理解 JDK 8 之前编译不带参数名的问题,明确 @Param 的解决价值
  • 学会在多参数查询、Map 参数、集合参数场景中正确使用 @Param
  • 明确 @Param 中声明的名称与 #{} 占位符的对应关系

定义

@Param 是 MyBatis 提供的参数命名注解,标注在 Mapper 接口方法的形参上,用于为参数指定一个自定义名称。该名称可在 @Select、@Insert、@Update、@Delete 的 SQL 中通过 #{} 或 ${} 引用。

痛点解决:MyBatis 默认通过反射获取方法参数名,但 JDK 8 之前的编译器默认不保留参数名信息(不带 -parameters 编译选项),导致多参数时 MyBatis 无法识别参数名,只能以 arg0、arg1 或 param1、param2 作为默认键。@Param 通过显式命名彻底解决了这一问题,使 SQL 中的参数引用清晰可控。

注解方式 vs XML 方式对比

对比维度@Param 注解方式XML 方式
参数命名通过 @Param("name") 显式命名XML 中直接通过 #{name} 引用,参数名依赖编译配置
多参数支持必须加 @Param 才能安全引用同样建议加 @Param,行为一致
Map 参数@Param("map") 后通过 #{map.key} 引用行为一致
适用性注解和 XML 均可使用注解和 XML 均可使用

适用场景建议:无论使用注解还是 XML,只要方法有多个参数,都建议显式添加 @Param,避免 JDK 版本和编译配置差异导致的参数绑定失败。

适用位置与核心属性

@Param 标注在 Mapper 接口方法的形参 上。

属性类型必填说明
valueString是参数在 SQL 中的引用名称。当只有一个属性时,可省略 value = 直接写字符串

核心原理

MyBatis 在调用 Mapper 方法时,会将所有参数封装到一个 ParamMap 中。如果参数加了 @Param("name"),则以该名称作为键存入 Map;如果没有加 @Param,JDK 8+ 且编译带 -parameters 时以实际参数名为键,否则以 arg0、arg1... 或 param1、param2... 为键。SQL 解析时,#{name} 会从 ParamMap 中取出对应键的值进行绑定。

完整示例

场景说明

乐途公司学生管理系统需要实现:根据名称和年龄范围查询学生;同时演示 Map 参数和集合参数的 @Param 用法。

操作前的数据库表结构及初始数据

CREATE TABLE student (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20),
    age INT,
    major VARCHAR(20),
    score DECIMAL(5,2)
);

INSERT INTO student (name, age, major, score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);

当前数据状态:

idnameagemajorscore
1大翔22计算机科学95.50
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00

完整的注解接口代码

实体类

package com.flying.entity;

public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String major;
    private Double score;

    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    public String getMajor() { return major; }
    public void setMajor(String major) { this.major = major; }
    public Double getScore() { return score; }
    public void setScore(Double score) { this.score = score; }

    @Override
    public String toString() {
        return "Student{id=" + id + ", name='" + name + "', age=" + age +
               ", major='" + major + "', score=" + score + "}";
    }
}

Mapper 接口

package com.flying.mapper;

import com.flying.entity.Student;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;

public interface StudentMapper {

    /**
     * 根据名称和年龄范围查询学生
     * 多参数必须加 @Param,否则 JDK 8 之前无法识别参数名
     */
    @Select("SELECT id, name, age, major, score FROM student WHERE name LIKE CONCAT('%', #{name}, '%') AND age BETWEEN #{minAge} AND #{maxAge}")
    List<Student> selectByNameAndAgeRange(@Param("name") String name,
                                          @Param("minAge") Integer minAge,
                                          @Param("maxAge") Integer maxAge);

    /**
     * 使用 Map 作为参数,@Param 指定 Map 在 SQL 中的前缀
     */
    @Select("SELECT id, name, age, major, score FROM student WHERE major = #{params.major} AND score >= #{params.minScore}")
    List<Student> selectByMap(@Param("params") Map<String, Object> params);

    /**
     * 根据 ID 列表批量查询
     */
    @Select("<script>SELECT id, name, age, major, score FROM student WHERE id IN " +
            "<foreach collection='ids' item='id' open='(' separator=',' close=')'>#{id}</foreach></script>")
    List<Student> selectByIds(@Param("ids") List<Integer> ids);
}

测试调用代码

package com.flying.test;

import com.flying.entity.Student;
import com.flying.mapper.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ParamTest {
    public static void main(String[] args) throws Exception {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = factory.openSession();

        StudentMapper mapper = session.getMapper(StudentMapper.class);

        // 1. 多参数查询:名称包含"小",年龄在 20 到 22 之间
        System.out.println("=== 多参数查询 ===");
        List<Student> list1 = mapper.selectByNameAndAgeRange("小", 20, 22);
        list1.forEach(System.out::println);

        // 2. Map 参数查询:专业为"计算机科学",分数 >= 90
        System.out.println("\n=== Map 参数查询 ===");
        Map<String, Object> params = new HashMap<>();
        params.put("major", "计算机科学");
        params.put("minScore", 90.0);
        List<Student> list2 = mapper.selectByMap(params);
        list2.forEach(System.out::println);

        // 3. 集合参数查询:查询 ID 为 1, 3, 5 的学生
        System.out.println("\n=== 集合参数查询 ===");
        List<Integer> ids = Arrays.asList(1, 3, 5);
        List<Student> list3 = mapper.selectByIds(ids);
        list3.forEach(System.out::println);

        session.close();
    }
}

实际执行结果

控制台 SQL 输出

=== 多参数查询 ===
[main] DEBUG com.flying.mapper.StudentMapper.selectByNameAndAgeRange - ==>  Preparing: SELECT id, name, age, major, score FROM student WHERE name LIKE CONCAT('%', ?, '%') AND age BETWEEN ? AND ?
[main] DEBUG com.flying.mapper.StudentMapper.selectByNameAndAgeRange - ==> Parameters: 小(String), 20(Integer), 22(Integer)
[main] DEBUG com.flying.mapper.StudentMapper.selectByNameAndAgeRange - <==      Total: 2
Student{id=3, name='小崔', age=20, major='计算机科学', score=92.0}
Student{id=5, name='李眉', age=22, major='软件工程', score=87.0}

=== Map 参数查询 ===
[main] DEBUG com.flying.mapper.StudentMapper.selectByMap - ==>  Preparing: SELECT id, name, age, major, score FROM student WHERE major = ? AND score >= ?
[main] DEBUG com.flying.mapper.StudentMapper.selectByMap - ==> Parameters: 计算机科学(String), 90.0(Double)
[main] DEBUG com.flying.mapper.StudentMapper.selectByMap - <==      Total: 2
Student{id=1, name='大翔', age=22, major='计算机科学', score=95.5}
Student{id=3, name='小崔', age=20, major='计算机科学', score=92.0}

=== 集合参数查询 ===
[main] DEBUG com.flying.mapper.StudentMapper.selectByIds - ==>  Preparing: SELECT id, name, age, major, score FROM student WHERE id IN ( ? , ? , ? )
[main] DEBUG com.flying.mapper.StudentMapper.selectByIds - ==> Parameters: 1(Integer), 3(Integer), 5(Integer)
[main] DEBUG com.flying.mapper.StudentMapper.selectByIds - <==      Total: 3
Student{id=1, name='大翔', age=22, major='计算机科学', score=95.5}
Student{id=3, name='小崔', age=20, major='计算机科学', score=92.0}
Student{id=5, name='李眉', age=22, major='软件工程', score=87.0}

查询结果集表格

查询方法参数结果
selectByNameAndAgeRangename="小", minAge=20, maxAge=22小崔、李眉
selectByMapmajor="计算机科学", minScore=90.0大翔、小崔
selectByIds[1, 3, 5]大翔、小崔、李眉

分析

  • @Param("name") 声明的参数名必须与 SQL 中的 #{name} 完全一致,这是多参数绑定的关键
  • Map 参数通过 @Param("params") 命名后,SQL 中通过 #{params.major} 访问 Map 中的键,实现了动态条件的封装
  • 集合参数在 <foreach> 中通过 @Param("ids") 命名后,collection='ids' 才能正确解析,否则会出现 "Expression 'ids' not found" 异常

易错场景 / 常见误区

误区错误示例正解
多参数不加 @ParamList<Student> select(String name, Integer age)写成 select(@Param("name") String name, @Param("age") Integer age)
@Param 名称与 #{} 不一致@Param("stuName") 但 SQL 写 #{name}两者必须完全一致
Map 参数不加 @ParamselectByMap(Map<String, Object> params)写成 selectByMap(@Param("params") Map<String, Object> params)
认为 JDK 8+ 不需要 @Param—即使 JDK 8+,如果编译未加 -parameters,仍需 @Param

面试考点

Q1:为什么多参数场景下建议始终使用 @Param?

JDK 8 之前编译默认不带参数名信息,MyBatis 无法通过反射获取真实参数名,只能使用 arg0、arg1 或 param1、param2 作为默认键。加 @Param 后,参数名与 SQL 中的 #{} 明确对应,不受 JDK 版本和编译选项影响,代码可移植性更强。

Q2:@Param 中的名称和 #{} 中的名称必须一致吗?

必须一致。#{name} 会从 ParamMap 中查找键为 name 的值,如果 @Param 声明的是 stuName,而 SQL 中写的是 #{name},运行时会出现 "Expression 'name' not found" 的绑定异常。

Q3:单个参数时是否需要加 @Param?

一般不需要。单个参数时,MyBatis 会直接将该参数作为唯一值绑定到 #{} 中,无论 #{} 里写什么名称(如 #{id}、#{abc})都能绑定成功。但如果参数是集合或数组,且要在 <foreach> 中通过 collection 引用,则必须加 @Param 明确命名。

Q4:@Param 可以用在 XML 映射的方式中吗?

可以。@Param 是 Mapper 接口层面的注解,与 SQL 写在注解中还是 XML 中无关。只要 Mapper 接口方法的参数加了 @Param,XML 中的 #{name} 同样可以正确引用。

小结

@Param 是注解开发和 XML 开发中都不可或缺的参数命名工具。它解决了 JDK 版本差异带来的参数名丢失问题,使多参数、Map 参数、集合参数的绑定变得清晰可控。建议养成多参数必加 @Param 的编码习惯。

下一章引子

插入数据时,获取数据库生成的主键是常见需求。@Options 注解提供了语句级的配置能力,常与 @Insert 配合实现主键自动回填。下一节将详细讲解 @Options 的用法。

上一页
@Delete
下一页
@Options