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

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

resultType

导学

本节将掌握MyBatis中resultType属性的工作原理与适用边界。你将学会如何在select元素中使用resultType映射查询结果,理解自动映射的底层机制,掌握基本类型、包装类型、POJO和Map四种返回形式,并明确列名与属性名不一致时resultType的局限性。

定义

resultType是select元素的属性,用于指定查询结果期望映射到的Java类型。在JDBC原始写法中,开发者需要手动遍历ResultSet,逐列调用getInt()、getString()等方法,再通过反射或构造函数将数据填充到Java对象。resultType将这一繁琐过程自动化:MyBatis根据指定的类型,利用反射自动将数据库列名与Java属性名匹配,完成结果集到对象的批量转换。

适用位置与核心属性

resultType作为select元素的属性出现,与resultMap互斥。

属性是否必填说明
resultType否*期望返回结果映射到的类名或别名(与resultMap二选一)
resultMap否*引用外部resultMap定义的ID(与resultType二选一)

*resultType与resultMap必须且只能配置一个。

resultType可接受的类型分为四类:

类型类别示例适用场景
基本类型int、long、boolean统计查询,如COUNT(*)
包装类型java.lang.Integer、java.lang.String单值查询,如查询姓名
POJOcom.flywing.entity.Student多列映射到实体对象
Mapjava.util.HashMap动态结构,列名作为键

核心原理

MyBatis的resultType自动映射遵循一套明确的规则:先查找与列名完全一致的属性,再尝试驼峰转换,最后通过类型处理器完成Java类型与JDBC类型的转换。

  1. 列名匹配:MyBatis读取ResultSetMetaData获取列名,与resultType指定类的属性名进行比对。
  2. 精确匹配:若列名name与属性name完全一致,直接通过反射调用setter方法赋值。
  3. 驼峰映射:若mybatis-config.xml中配置了mapUnderscoreToCamelCase=true,列名stu_name会自动映射到属性stuName。
  4. 类型转换:通过TypeHandlerRegistry查找合适的TypeHandler,将JDBC类型(如VARCHAR、DECIMAL)转换为Java类型(如String、Double)。
  5. 映射失败:若列名与属性名既不一致也无法通过驼峰转换匹配,该属性保持默认值(引用类型为null,基本类型为0/false)。

完整示例

场景说明

乐途公司技术部需要统计学员信息:查询总人数、查询某学员姓名、查询学员完整信息列表、以及以Map形式灵活获取数据。本节演示resultType的四种典型用法,并暴露列名不一致时的映射问题。

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

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

完整的映射文件片段与Java代码

POJO类(列名与属性名一致)

package com.flywing.entity;

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

    // Getter与Setter省略
}

POJO类(列名与属性名不一致,用于演示问题)

package com.flywing.entity;

public class StudentMismatch {
    private Integer stuId;      // 对应数据库列 id
    private String stuName;     // 对应数据库列 name
    private Integer stuAge;     // 对应数据库列 age
    // Getter与Setter省略
}

Mapper接口

package com.flywing.mapper;

import com.flywing.entity.Student;
import com.flywing.entity.StudentMismatch;
import java.util.List;
import java.util.Map;

public interface StudentMapper {
    // 返回基本类型:统计总人数
    int countAll();

    // 返回包装类型:查询姓名
    String findNameById(Integer id);

    // 返回POJO列表
    List<Student> findAll();

    // 返回Map
    List<Map<String, Object>> findAllAsMapList();

    // 列名不一致时的映射(演示问题)
    StudentMismatch findMismatchById(Integer id);
}

映射文件 StudentMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.flywing.mapper.StudentMapper">

    <!-- 场景一:返回基本类型 int -->
    <select id="countAll" resultType="int">
        SELECT COUNT(*) FROM student
    </select>

    <!-- 场景二:返回包装类型 String -->
    <select id="findNameById" resultType="java.lang.String">
        SELECT name FROM student WHERE id = #{id}
    </select>

    <!-- 场景三:返回POJO列表 -->
    <select id="findAll" resultType="com.flywing.entity.Student">
        SELECT id, name, age, major, score FROM student
    </select>

    <!-- 场景四:返回Map列表 -->
    <select id="findAllAsMapList" resultType="java.util.HashMap">
        SELECT id, name, age, major, score FROM student
    </select>

    <!-- 场景五:列名不一致,resultType自动映射失效 -->
    <select id="findMismatchById" resultType="com.flywing.entity.StudentMismatch">
        SELECT id, name, age FROM student WHERE id = #{id}
    </select>

</mapper>

测试代码

package com.flywing.test;

import com.flywing.entity.Student;
import com.flywing.entity.StudentMismatch;
import com.flywing.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;
import java.util.Map;

public class ResultTypeTest {
    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);

        // 场景一:基本类型
        int total = mapper.countAll();
        System.out.println("学员总人数:" + total);

        // 场景二:包装类型
        String name = mapper.findNameById(1);
        System.out.println("id=1的学员姓名:" + name);

        // 场景三:POJO列表
        List<Student> list = mapper.findAll();
        System.out.println("POJO方式查询结果:");
        for (Student s : list) {
            System.out.println("  " + s.getName() + " | " + s.getMajor() + " | " + s.getScore());
        }

        // 场景四:Map列表
        List<Map<String, Object>> mapList = mapper.findAllAsMapList();
        System.out.println("Map方式查询结果:");
        for (Map<String, Object> map : mapList) {
            System.out.println("  " + map.get("name") + " | " + map.get("major"));
        }

        // 场景五:列名不一致问题
        StudentMismatch mm = mapper.findMismatchById(1);
        System.out.println("列名不一致映射结果:");
        System.out.println("  stuId=" + mm.getStuId() + ",stuName=" + mm.getStuName() + ",stuAge=" + mm.getStuAge());

        session.close();
    }
}

实际执行结果

控制台SQL输出

[DEBUG] com.flywing.mapper.StudentMapper.countAll - ==>  Preparing: SELECT COUNT(*) FROM student
[DEBUG] com.flywing.mapper.StudentMapper.countAll - <==      Total: 1
学员总人数:5

[DEBUG] com.flywing.mapper.StudentMapper.findNameById - ==>  Preparing: SELECT name FROM student WHERE id = ?
[DEBUG] com.flywing.mapper.StudentMapper.findNameById - ==> Parameters: 1(Integer)
[DEBUG] com.flywing.mapper.StudentMapper.findNameById - <==      Total: 1
id=1的学员姓名:大翔

[DEBUG] com.flywing.mapper.StudentMapper.findAll - ==>  Preparing: SELECT id, name, age, major, score FROM student
[DEBUG] com.flywing.mapper.StudentMapper.findAll - <==      Total: 5
POJO方式查询结果:
  大翔 | 计算机科学 | 95.5
  白歌 | 软件工程 | 88.0
  小崔 | 计算机科学 | 92.0
  黄俪 | 信息安全 | 90.5
  李眉 | 软件工程 | 87.0

[DEBUG] com.flywing.mapper.StudentMapper.findAllAsMapList - ==>  Preparing: SELECT id, name, age, major, score FROM student
[DEBUG] com.flywing.mapper.StudentMapper.findAllAsMapList - <==      Total: 5
Map方式查询结果:
  大翔 | 计算机科学
  白歌 | 软件工程
  ...

[DEBUG] com.flywing.mapper.StudentMapper.findMismatchById - ==>  Preparing: SELECT id, name, age FROM student WHERE id = ?
[DEBUG] com.flywing.mapper.StudentMapper.findMismatchById - ==> Parameters: 1(Integer)
[DEBUG] com.flywing.mapper.StudentMapper.findMismatchById - <==      Total: 1
列名不一致映射结果:
  stuId=null,stuName=null,stuAge=null

查询结果集表格(以findAll为例)

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

分析

  1. 基本类型与包装类型:COUNT(*)返回int,单值查询name返回String。MyBatis通过ResultSet.getInt(1)和ResultSet.getString(1)直接提取第一列的值。
  2. POJO自动映射:Student的属性名id、name、age、major、score与数据库列名完全一致,MyBatis通过反射调用setId()、setName()等完成赋值,无需额外配置。
  3. Map返回:resultType="java.util.HashMap"时,每一行数据被封装为Map<String, Object>,列名作为键,列值作为值。这种方式灵活但失去了类型安全,适合临时查询或动态报表场景。
  4. 列名不一致的陷阱:StudentMismatch的属性stuId、stuName、stuAge与数据库列id、name、age不一致,且未开启驼峰映射,导致所有属性均为null。这是resultType的最大局限,也是resultMap存在的意义。

易错场景/常见误区

误区正解
认为resultType可以处理任意列名与属性名的差异resultType只能处理完全一致或驼峰转换后的匹配;其他情况必须用resultMap
查询多列但resultType写基本类型多列查询应映射到POJO或Map,基本类型只能接收单列单值
返回Map时认为键是驼峰命名Map的键是数据库原始列名(如stu_name),不是驼峰转换后的名字
同时配置resultType和resultMap二者互斥,同时存在会导致解析异常
认为resultType支持泛型XML中resultType写具体类名,如java.util.HashMap;接口返回List<Map<String, Object>>即可

面试考点

Q1:resultType和resultMap有什么区别?

resultType适用于列名与Java属性名完全一致(或可通过驼峰转换匹配)的简单场景,MyBatis自动完成映射。resultMap适用于列名与属性名不一致、需要复杂类型转换、嵌套关联映射等场景,由开发者显式定义每一列与属性的对应关系。二者互斥,必须且只能使用一个。

Q2:数据库列名是user_name,Java属性是userName,resultType能自动映射吗?

默认不能。需要在mybatis-config.xml中开启mapUnderscoreToCamelCase=true,MyBatis才会将下划线命名自动转换为驼峰命名。否则该属性映射失败,值为null。

Q3:resultType="java.util.HashMap"时,Map的键是什么类型?

键是String类型,值为数据库原始列名(大写或小写取决于数据库驱动);值是Object类型,为对应列的JDBC返回值。注意不同数据库驱动对列名大小写的处理可能不同,MySQL通常返回小写。

Q4:为什么resultType在列名不一致时属性为null而不是报错?

MyBatis的自动映射策略是"尽力而为":能匹配的属性赋值,不能匹配的保持默认值(引用类型为null)。这种设计保证了查询不会因个别字段映射失败而整体中断,但也导致问题难以发现。生产环境建议开启autoMappingBehavior=FULL配合日志检查未映射的列。

小结

resultType是MyBatis中最便捷的结果映射方式,适用于列名与属性名一致的简单场景。它支持基本类型、包装类型、POJO和Map四种返回形态,通过自动反射和类型处理器完成结果集转换。但其核心局限在于无法处理列名与属性名不一致的情况,此时所有不匹配的属性将保持null。要解决这一问题,需要引入resultMap进行显式映射配置。

下一章引子

当resultType的自动映射失效时,开发者需要一种更精细的控制手段来告诉MyBatis:数据库的哪一列对应Java对象的哪一个属性,甚至如何处理类型转换。resultMap正是为此而生。它通过id、result等子元素显式定义映射规则,支持继承、自动映射控制等高级特性。下一节将深入讲解resultMap的结构与用法,彻底解决列名不一致的映射难题。

上一页
主键生成策略
下一页
resultMap