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 | 单值查询,如查询姓名 |
| POJO | com.flywing.entity.Student | 多列映射到实体对象 |
| Map | java.util.HashMap | 动态结构,列名作为键 |
核心原理
MyBatis的resultType自动映射遵循一套明确的规则:先查找与列名完全一致的属性,再尝试驼峰转换,最后通过类型处理器完成Java类型与JDBC类型的转换。
- 列名匹配:MyBatis读取
ResultSetMetaData获取列名,与resultType指定类的属性名进行比对。 - 精确匹配:若列名
name与属性name完全一致,直接通过反射调用setter方法赋值。 - 驼峰映射:若
mybatis-config.xml中配置了mapUnderscoreToCamelCase=true,列名stu_name会自动映射到属性stuName。 - 类型转换:通过
TypeHandlerRegistry查找合适的TypeHandler,将JDBC类型(如VARCHAR、DECIMAL)转换为Java类型(如String、Double)。 - 映射失败:若列名与属性名既不一致也无法通过驼峰转换匹配,该属性保持默认值(引用类型为
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);
当前数据状态:
| 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 |
完整的映射文件片段与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为例)
| 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 |
分析
- 基本类型与包装类型:
COUNT(*)返回int,单值查询name返回String。MyBatis通过ResultSet.getInt(1)和ResultSet.getString(1)直接提取第一列的值。 - POJO自动映射:
Student的属性名id、name、age、major、score与数据库列名完全一致,MyBatis通过反射调用setId()、setName()等完成赋值,无需额外配置。 - Map返回:
resultType="java.util.HashMap"时,每一行数据被封装为Map<String, Object>,列名作为键,列值作为值。这种方式灵活但失去了类型安全,适合临时查询或动态报表场景。 - 列名不一致的陷阱:
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的结构与用法,彻底解决列名不一致的映射难题。