@Results
导学
本节学习目标:
- 掌握
@Results注解的基本用法,能够替代 XML 中的<resultMap>标签完成结果映射 - 理解
@Results的id属性作用,学会配合@ResultMap实现映射复用 - 明确
@Results与@Result的关系:@Results是容器,@Result是元素 - 掌握列名与属性名不一致时的注解映射方案
定义
@Results 是 MyBatis 提供的结果映射列表注解,标注在 Mapper 接口的方法上,用于声明一组结果映射规则。它对应 XML 映射文件中的 <resultMap> 元素,内部通过 value 属性包含多个 @Result 注解。
痛点解决:当数据库列名与 Java 实体类属性名不一致时(如数据库用 user_name,Java 用 userName),MyBatis 无法自动映射。@Results 提供了一套注解化的显式映射机制,让开发者在不编写 XML 的情况下也能精确控制每一列与属性的对应关系。
注解方式 vs XML 方式对比
| 对比维度 | @Results 注解方式 | XML <resultMap> 方式 |
|---|---|---|
| 映射声明 | 写在接口方法上方,与 SQL 紧邻 | 写在独立 XML 中,与接口分离 |
| 复用方式 | 通过 id 命名,供 @ResultMap 引用 | 通过 id 命名,供 resultMap 属性引用 |
| 可读性 | 字段少时直观,字段多时注解堆叠 | 字段多时 XML 结构更清晰 |
| 维护成本 | 修改映射需改接口 | 修改 XML 无需重新编译 |
适用场景建议:列名与属性名不一致的表优先使用
@Results;映射规则极复杂(如多层嵌套、鉴别器)时建议使用 XML<resultMap>。
适用位置与核心属性
@Results 标注在 Mapper 接口的方法 上,通常与 @Select 配合使用。
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | String | 否 | 为该结果映射命名,供 @ResultMap 引用,实现映射复用 |
value | Result[] | 是 | 包含多个 @Result 注解,每个 @Result 定义一条列到属性的映射规则 |
核心原理
MyBatis 在解析 @Results 时,会将其内部的每个 @Result 转换为 ResultMapping 对象,并封装成一个 ResultMap 注册到 Configuration 中。如果声明了 id,该 ResultMap 会被命名缓存,其他方法可通过 @ResultMap("id") 直接引用,避免重复定义。查询执行时,MyBatis 根据 ResultMap 中的规则逐列映射到 Java 对象属性。
完整示例
场景说明
乐途公司学生管理系统的数据库表字段命名采用下划线风格(stu_name、stu_age),而 Java 实体类采用驼峰命名(stuName、stuAge)。需要通过 @Results 建立显式映射关系,并演示映射复用。
操作前的数据库表结构及初始数据
CREATE TABLE student (
stu_id INT PRIMARY KEY AUTO_INCREMENT,
stu_name VARCHAR(20),
stu_age INT,
stu_major VARCHAR(20),
stu_score DECIMAL(5,2)
);
INSERT INTO student (stu_name, stu_age, stu_major, stu_score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);
当前数据状态:
| stu_id | stu_name | stu_age | stu_major | stu_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 stuId;
private String stuName;
private Integer stuAge;
private String stuMajor;
private Double stuScore;
public Integer getStuId() { return stuId; }
public void setStuId(Integer stuId) { this.stuId = stuId; }
public String getStuName() { return stuName; }
public void setStuName(String stuName) { this.stuName = stuName; }
public Integer getStuAge() { return stuAge; }
public void setStuAge(Integer stuAge) { this.stuAge = stuAge; }
public String getStuMajor() { return stuMajor; }
public void setStuMajor(String stuMajor) { this.stuMajor = stuMajor; }
public Double getStuScore() { return stuScore; }
public void setStuScore(Double stuScore) { this.stuScore = stuScore; }
@Override
public String toString() {
return "Student{stuId=" + stuId + ", stuName='" + stuName + "', stuAge=" + stuAge +
", stuMajor='" + stuMajor + "', stuScore=" + stuScore + "}";
}
}
Mapper 接口
package com.flying.mapper;
import com.flying.entity.Student;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface StudentMapper {
/**
* 查询所有学生,使用 @Results 显式映射下划线列名到驼峰属性
* 并给该映射命名 id="studentMap",供其他方法复用
*/
@Select("SELECT stu_id, stu_name, stu_age, stu_major, stu_score FROM student")
@Results(id = "studentMap", value = {
@Result(column = "stu_id", property = "stuId", id = true),
@Result(column = "stu_name", property = "stuName"),
@Result(column = "stu_age", property = "stuAge"),
@Result(column = "stu_major", property = "stuMajor"),
@Result(column = "stu_score", property = "stuScore")
})
List<Student> selectAll();
/**
* 根据 ID 查询学生,复用上面定义的 studentMap
*/
@Select("SELECT stu_id, stu_name, stu_age, stu_major, stu_score FROM student WHERE stu_id = #{id}")
@ResultMap("studentMap")
Student selectById(Integer id);
}
测试调用代码
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.List;
public class ResultsTest {
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. 查询所有学生,验证 @Results 映射
System.out.println("=== 查询所有学生 ===");
List<Student> all = mapper.selectAll();
all.forEach(System.out::println);
// 2. 根据 ID 查询,验证 @ResultMap 复用
System.out.println("\n=== 根据 ID 查询 ===");
Student stu = mapper.selectById(1);
System.out.println(stu);
session.close();
}
}
实际执行结果
控制台 SQL 输出
=== 查询所有学生 ===
[main] DEBUG com.flying.mapper.StudentMapper.selectAll - ==> Preparing: SELECT stu_id, stu_name, stu_age, stu_major, stu_score FROM student
[main] DEBUG com.flying.mapper.StudentMapper.selectAll - ==> Parameters:
[main] DEBUG com.flying.mapper.StudentMapper.selectAll - <== Total: 5
Student{stuId=1, stuName='大翔', stuAge=22, stuMajor='计算机科学', stuScore=95.5}
Student{stuId=2, stuName='白歌', stuAge=21, stuMajor='软件工程', stuScore=88.0}
Student{stuId=3, stuName='小崔', stuAge=20, stuMajor='计算机科学', stuScore=92.0}
Student{stuId=4, stuName='黄俪', stuAge=21, stuMajor='信息安全', stuScore=90.5}
Student{stuId=5, stuName='李眉', stuAge=22, stuMajor='软件工程', stuScore=87.0}
=== 根据 ID 查询 ===
[main] DEBUG com.flying.mapper.StudentMapper.selectById - ==> Preparing: SELECT stu_id, stu_name, stu_age, stu_major, stu_score FROM student WHERE stu_id = ?
[main] DEBUG com.flying.mapper.StudentMapper.selectById - ==> Parameters: 1(Integer)
[main] DEBUG com.flying.mapper.StudentMapper.selectById - <== Total: 1
Student{stuId=1, stuName='大翔', stuAge=22, stuMajor='计算机科学', stuScore=95.5}
查询结果集表格
| 查询方法 | 结果 |
|---|---|
| selectAll | 5 条记录,stuName 正确映射为大翔、白歌、小崔、黄俪、李眉 |
| selectById(1) | 1 条记录:大翔,验证 @ResultMap 复用成功 |
分析
@Results的id = "studentMap"是该映射的全局标识,注册到 Configuration 后可在同一命名空间下被@ResultMap("studentMap")引用@Result(column = "stu_id", property = "stuId", id = true)中的id = true表示该列是主键,MyBatis 在缓存和嵌套映射时会优先使用主键做对象识别- 如果不使用
@Results显式映射,下划线列名无法自动匹配驼峰属性,查询结果中所有属性都会为null
易错场景 / 常见误区
| 误区 | 错误示例 | 正解 |
|---|---|---|
| 认为驼峰命名会自动映射下划线列名 | 不加 @Results 直接查询 | 开启 mapUnderscoreToCamelCase=true 或显式使用 @Results |
| @ResultMap 引用时写错 id | @ResultMap("studentmap") | 区分大小写,必须与 @Results(id = "studentMap") 完全一致 |
| 忘记标记主键 | 不写 id = true | 主键列的 @Result 必须加 id = true,否则影响缓存和嵌套映射 |
| 在 @Results 中写 SQL | — | @Results 只负责映射,SQL 写在 @Select 中 |
面试考点
Q1:@Results 的 id 属性有什么作用?
id用于给当前结果映射命名,注册到 Configuration 后,同一 Mapper 接口的其他方法可以通过@ResultMap("id")复用该映射,避免重复书写相同的@Result列表。
Q2:如果不使用 @Results,下划线列名和驼峰属性名能自动映射吗?
默认不能。需要在
mybatis-config.xml中开启mapUnderscoreToCamelCase=true,此时 MyBatis 会自动将stu_name映射为stuName。若未开启该配置,则必须通过@Results显式声明映射关系。
Q3:@Results 可以定义在 XML 中、被注解引用吗?
可以。
@ResultMap引用的 id 可以是 XML 中定义的<resultMap>的 id,前提是 XML 映射文件已正确加载且命名空间一致。这体现了注解与 XML 混用的灵活性。
Q4:@Results 和 @Result 是什么关系?
@Results是容器注解,对应 XML 的<resultMap>;@Result是元素注解,对应 XML 的<id>和<result>。一个@Results内部通过value属性包含多个@Result。
小结
@Results 是注解开发中解决列名与属性名不一致问题的核心方案。通过 id 命名实现映射复用,可以显著减少重复代码。对于字段较多的表,建议评估是否开启全局 mapUnderscoreToCamelCase 配置,以进一步简化映射声明。
下一章引子
@Results 是容器,真正定义每一条列到属性映射规则的是 @Result。下一节将深入讲解 @Result 的各个属性及其在单条映射中的精细控制。