好的,这是一个非常核心的MyBatis面试题和日常开发中的关键知识点。resultMap
和 resultType
都用于指定SQL查询结果的返回类型,但它们的用法和能力有本质区别。
下面我用一个清晰的对比和示例来帮你彻底理解。
一、核心区别总结
特性 | resultType |
resultMap |
---|---|---|
映射方式 | 自动映射 (基于约定) | 手动映射 (基于显式配置) |
控制力 | 弱,MyBatis自动完成,你无法干预过程 | 强,你完全掌控映射的每一个细节 |
配置 | 简单,只需指定一个Java类的全限定名 | 复杂,需要额外编写 <resultMap> 标签 |
列名 vs 属性名 | 必须一致(或开启驼峰命名规则) | 可以任意不同,你手动指定对应关系 |
复杂关系 | 无法处理关联对象(如一对一、一对多) | 可以处理复杂的关联查询和嵌套结果 |
适用场景 | 简单查询、字段名与属性名完全对应 | 几乎所有正式项目场景,尤其是不一致和有关联时 |
二、工作原理对比
1. resultType
(自动映射)
当你使用 resultType
时,你是在对MyBatis说:
“嗨,MyBatis,这是我想要的结果类型(例如
com.example.User
)。你自己想办法把查询结果的列名和我这个类里的属性名对应上吧。”
-
如何对应? MyBatis会查看查询结果返回的列名(
user_name
,user_age
),然后尝试在Java类中寻找同名的属性(user_name
,user_age
)。如果开启了mapUnderscoreToCamelCase
配置,它会尝试将下划线命名转换为驼峰命名(user_name
->userName
)。 -
风险:如果数据库列名和Java属性名不匹配,对应的属性就会为
null
,而且不会有任何错误提示,这是一个潜在的Bug源。
示例:
<select id="findUser" resultType="com.example.User">SELECT user_id, user_name, user_age FROM t_user WHERE user_id = #{id}
</select>
public class User {private Integer userId; // 能映射上 (开启驼峰规则后 user_id -> userId)private String userName; // 能映射上 (开启驼峰规则后 user_name -> userName)private Integer age; // 映射失败!数据库返回的是 user_age,但属性是 age。最终 age=null。
}
2. resultMap
(手动映射)
当你使用 resultMap
时,你是在对MyBatis说:
“嗨,MyBatis,我亲自告诉你数据库的列和Java类的属性是怎么对应的。你就严格按照我给的这份‘说明书’(即
<resultMap>
)来组装对象。”
你完全掌控了映射过程,无论名字是否一样。
示例:
<!-- 首先,定义一份“说明书” -->
<resultMap id="UserResultMap" type="com.example.User"><id property="userId" column="user_id" /> <!-- 指定主键映射 --><result property="userName" column="user_name" /> <!-- 指定普通字段映射 --><result property="age" column="user_age" /> <!-- 即使名字完全不同,也能正确映射 -->
</resultMap><!-- 然后,在SQL中引用这份说明书 -->
<select id="findUser" resultMap="UserResultMap">SELECT user_id, user_name, user_age FROM t_user WHERE user_id = #{id}
</select>
public class User {private Integer userId;private String userName;private Integer age; // 现在这个属性也能正确接收到 user_age 列的值了!
}
三、核心能力差异详解
1. 处理关联关系(这是 resultMap
的杀手锏)
resultType
完全无法处理这种情况,而 resultMap
可以轻松搞定。
场景:查询用户信息的同时,获取他所在的部门信息(一个用户属于一个部门)。
// Java实体类
public class User {private Integer id;private String name;private Department department; // 一个用户关联一个部门对象
}
public class Department {private Integer id;private String deptName;
}
使用 resultMap
的解决方案:
<!-- 1. 先定义Department的映射 -->
<resultMap id="DepartmentResultMap" type="Department"><id property="id" column="dept_id"/><result property="deptName" column="dept_name"/>
</resultMap><!-- 2. 再定义User的映射,并关联Department -->
<resultMap id="UserWithDeptResultMap" type="User"><id property="id" column="user_id"/><result property="name" column="user_name"/><!-- 使用 association 处理“一对一”关联 --><association property="department" resultMap="DepartmentResultMap"/>
</resultMap><!-- 3. 使用关联查询SQL -->
<select id="findUserWithDepartment" resultMap="UserWithDeptResultMap">SELECTu.id as user_id,u.name as user_name,d.id as dept_id, <!-- 部门字段 -->d.name as dept_name <!-- 部门字段 -->FROM user uLEFT JOIN department d ON u.dept_id = d.idWHERE u.id = #{id}
</select>
这样一次查询,返回的 User
对象里面的 department
属性就已经被完整地填充好了。
2. 处理集合关系(一对多)
场景:查询一个部门的同时,获取该部门下的所有员工。
public class Department {private Integer id;private String deptName;private List<User> employees; // 一个部门有多个员工
}
<resultMap id="DepartmentWithUsersResultMap" type="Department"><id property="id" column="dept_id"/><result property="deptName" column="dept_name"/><!-- 使用 collection 处理“一对多”关联 --><collection property="employees" ofType="User" resultMap="UserResultMap"/>
</resultMap>
四、如何选择?最佳实践
-
永远首选
resultMap
: 在正式的、复杂的项目开发中,几乎总是使用resultMap
。它提供了明确的映射关系,避免了因命名变化导致的隐性BUG,代码意图更清晰,可维护性更强。 -
何时用
resultType
: 仅用于非常简单的、快速的测试或原型开发,或者当你查询的只是一个简单的字段(如SELECT COUNT(*)
)时,你可以用resultType="java.lang.Integer"
。 -
开启驼峰命名: 即使你决定在某些简单场景使用
resultType
,也应在mybatis-config.xml
中设置<setting name="mapUnderscoreToCamelCase" value="true"/>
,让自动映射更智能。
结论:resultMap
是功能强大的手动模式,而 resultType
是简单但受限的自动模式。 把 resultMap
想象成专业相机的手动挡,一切参数由你控制;而 resultType
是自动挡,简单但无法应对复杂场景。对于严肃的开发,请使用手动挡(resultMap
)。