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

    • 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执行器类型
    • 分页插件

@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 配合使用。

属性类型必填说明
idString否为该结果映射命名,供 @ResultMap 引用,实现映射复用
valueResult[]是包含多个 @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_idstu_namestu_agestu_majorstu_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}

查询结果集表格

查询方法结果
selectAll5 条记录,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 的各个属性及其在单条映射中的精细控制。

上一页
@SelectKey
下一页
@Result