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

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

@Result

导学

本节学习目标:

  • 掌握 @Result 注解的每个核心属性,能够精确控制单列到单属性的映射行为
  • 理解 id = true 在结果映射中的特殊作用
  • 学会使用 javaType、jdbcType、typeHandler 处理类型不一致的映射场景
  • 明确 @Result 与 XML 中 <id>、<result> 的对应关系

定义

@Result 是 MyBatis 提供的单个结果映射注解,用于声明数据库的一列如何映射到 Java 对象的一个属性。它对应 XML 映射文件中的 <id>(当 id = true 时)和 <result>(当 id = false 或未指定时)。

痛点解决:在 @Results 容器中,每个 @Result 负责一条精细的映射规则。当列名与属性名不一致、或列的 JDBC 类型与 Java 属性类型需要特殊转换(如 VARCHAR 转 Enum)、或需要指定 TypeHandler 时,@Result 提供了属性级的完整控制能力。

注解方式 vs XML 方式对比

对比维度@Result 注解方式XML <id> / <result> 方式
声明位置写在 @Results 的 value 数组中写在 <resultMap> 内部
属性控制通过注解属性控制通过 XML 属性控制
可读性字段少时紧凑字段多时 XML 层级更清晰
功能完整性完全等价完全等价

适用场景建议:注解方式下,@Result 必须配合 @Results 使用;XML 方式下使用 <id> 和 <result>。两者功能完全等价,选择取决于项目整体采用注解还是 XML。

适用位置与核心属性

@Result 只能用在 @Results 注解的 value 属性中,不能单独标注在方法上。

属性类型必填说明
idboolean否是否为标识列(主键)。默认 false。设为 true 时对应 XML 的 <id>
columnString是数据库列名
propertyString是Java 对象属性名
javaTypeClass<?>否Java 属性的类型,通常可自动推断
jdbcTypeJdbcType否JDBC 类型枚举,用于特殊类型处理(如 NULL 值判断)
typeHandlerClass<? extends TypeHandler>否自定义类型处理器,负责列值与属性值的转换
one@One否一对一关联映射,对应 XML 的 <association>
many@Many否一对多集合映射,对应 XML 的 <collection>

核心原理

MyBatis 将每个 @Result 解析为一个 ResultMapping 对象。ResultMapping 中保存了列名、属性名、类型信息、TypeHandler 以及关联映射(one 或 many)。查询结果集返回后,MyBatis 遍历每一行数据,根据 ResultMapping 列表逐列提取值,通过反射调用 Setter 方法写入 Java 对象。如果声明了 id = true,该列值还会被用作对象在缓存中的唯一标识。

完整示例

场景说明

乐途公司学生管理系统的数据库表字段命名采用下划线风格,且 stu_score 列在数据库中以 DECIMAL 存储,但业务上需要映射为自定义的 Score 对象(包含 value 和 grade 两个属性)。需要通过 @Result 的 typeHandler 实现自定义转换。

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

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

完整的注解接口代码

自定义类型:Score

package com.flying.entity;

public class Score {
    private Double value;
    private String grade;

    public Score(Double value) {
        this.value = value;
        this.grade = value >= 90 ? "A" : (value >= 80 ? "B" : "C");
    }

    public Double getValue() { return value; }
    public String getGrade() { return grade; }

    @Override
    public String toString() {
        return "Score{value=" + value + ", grade='" + grade + "'}";
    }
}

自定义 TypeHandler

package com.flying.handler;

import com.flying.entity.Score;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class ScoreTypeHandler extends BaseTypeHandler<Score> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Score parameter, JdbcType jdbcType) throws SQLException {
        ps.setDouble(i, parameter.getValue());
    }

    @Override
    public Score getNullableResult(ResultSet rs, String columnName) throws SQLException {
        double val = rs.getDouble(columnName);
        return rs.wasNull() ? null : new Score(val);
    }

    @Override
    public Score getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        double val = rs.getDouble(columnIndex);
        return rs.wasNull() ? null : new Score(val);
    }

    @Override
    public Score getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        double val = cs.getDouble(columnIndex);
        return cs.wasNull() ? null : new Score(val);
    }
}

实体类

package com.flying.entity;

public class Student {
    private Integer stuId;
    private String stuName;
    private Integer stuAge;
    private String stuMajor;
    private Score 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 Score getStuScore() { return stuScore; }
    public void setStuScore(Score 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 com.flying.handler.ScoreTypeHandler;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;

public interface StudentMapper {

    @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",
                    javaType = com.flying.entity.Score.class,
                    typeHandler = ScoreTypeHandler.class)
    })
    List<Student> selectAll();
}

测试调用代码

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 ResultTest {
    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);

        System.out.println("=== 查询所有学生(含自定义 TypeHandler 映射)===");
        List<Student> all = mapper.selectAll();
        all.forEach(System.out::println);

        session.close();
    }
}

实际执行结果

控制台 SQL 输出

=== 查询所有学生(含自定义 TypeHandler 映射)===
[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=Score{value=95.5, grade='A'}}
Student{stuId=2, stuName='白歌', stuAge=21, stuMajor='软件工程', stuScore=Score{value=88.0, grade='B'}}
Student{stuId=3, stuName='小崔', stuAge=20, stuMajor='计算机科学', stuScore=Score{value=92.0, grade='A'}}
Student{stuId=4, stuName='黄俪', stuAge=21, stuMajor='信息安全', stuScore=Score{value=90.5, grade='A'}}
Student{stuId=5, stuName='李眉', stuAge=22, stuMajor='软件工程', stuScore=Score{value=87.0, grade='B'}}

查询结果集表格

stu_idstu_namestu_agestu_majorstu_score (原始)stuScore (映射后)
1大翔22计算机科学95.50Score{value=95.5, grade='A'}
2白歌21软件工程88.00Score{value=88.0, grade='B'}
3小崔20计算机科学92.00Score{value=92.0, grade='A'}
4黄俪21信息安全90.50Score{value=90.5, grade='A'}
5李眉22软件工程87.00Score{value=87.0, grade='B'}

分析

  • @Result 的 id = true 将 stu_id 标记为主键,MyBatis 在缓存和对象识别时会优先使用该列
  • javaType 明确声明了目标类型为 Score.class,帮助 MyBatis 选择正确的类型处理路径
  • typeHandler = ScoreTypeHandler.class 指定了自定义转换器,将数据库的 DECIMAL 转换为包含等级逻辑的 Score 对象
  • 如果不指定 typeHandler,MyBatis 会尝试将 DECIMAL 直接赋给 Score 类型,导致类型不匹配异常

易错场景 / 常见误区

误区错误示例正解
主键列忘记加 id = true@Result(column="stu_id", property="stuId")主键必须写 id = true
typeHandler 类未注册只写 typeHandler = Xxx.class 但类路径错误确保 TypeHandler 类在类路径中,或在配置中全局注册
javaType 与实际属性类型冲突javaType = String.class 但属性是 IntegerjavaType 必须与属性声明类型一致
单独使用 @Result 而不放在 @Results 中方法上直接写 @Result@Result 必须放在 @Results(value = {...}) 内部

面试考点

Q1:@Result 中的 id = true 有什么作用?

标记该列是对象的主键。MyBatis 在结果缓存、嵌套映射去重、以及延迟加载的对象识别时,都会依赖主键值。如果主键列未标记 id = true,可能导致缓存重复对象或关联映射异常。

Q2:typeHandler 在什么场景下必须使用?

当数据库列类型与 Java 属性类型无法直接转换时使用。例如数据库存 VARCHAR 但 Java 属性是 Enum;数据库存 JSON 但 Java 属性是自定义对象;或者像本例中需要在提取值的同时附加业务计算(等级判定)。

Q3:@Result 的 one 和 many 属性分别对应 XML 的什么元素?

one 对应 XML 的 <association>,用于一对一关联映射;many 对应 XML 的 <collection>,用于一对多集合映射。这两个属性将在后续章节详细讲解。

Q4:jdbcType 属性一般什么时候需要显式指定?

当传入参数可能为 null 时,部分 JDBC 驱动无法推断 null 的类型,需要显式指定 jdbcType(如 jdbcType = JdbcType.VARCHAR),否则可能抛出 "Error setting null for parameter" 异常。

小结

@Result 是 @Results 容器中的最小映射单元,提供了从列名、属性名、类型到 TypeHandler 的完整控制链路。掌握 id = true 的主键语义、以及 typeHandler 的自定义转换能力,可以应对绝大多数非标准映射场景。

下一章引子

单列映射之外,实际业务中经常需要关联查询。@One 注解负责替代 XML 的 <association> 完成一对一关联映射,常用于学生关联导师、用户关联档案等场景。下一节将详细讲解 @One 的用法。

上一页
@Results
下一页
@One