@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 接口方法的形参 上。
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
value | String | 是 | 参数在 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);
当前数据状态:
| id | name | age | major | score |
|---|---|---|---|---|
| 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}
查询结果集表格
| 查询方法 | 参数 | 结果 |
|---|---|---|
| selectByNameAndAgeRange | name="小", minAge=20, maxAge=22 | 小崔、李眉 |
| selectByMap | major="计算机科学", 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" 异常
易错场景 / 常见误区
| 误区 | 错误示例 | 正解 |
|---|---|---|
| 多参数不加 @Param | List<Student> select(String name, Integer age) | 写成 select(@Param("name") String name, @Param("age") Integer age) |
| @Param 名称与 #{} 不一致 | @Param("stuName") 但 SQL 写 #{name} | 两者必须完全一致 |
| Map 参数不加 @Param | selectByMap(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 的用法。