
一、ResultMap 的定义
在当今的软件开发领域,MyBatis 作为一款优秀的持久层框架,以其简洁的配置和强大的功能,深受广大开发者的喜爱。然而,在实际的项目开发中,我们常常会遇到数据模型与数据库表结构不一致的情况,这时就需要 MyBatis 的 resultMap 功能来帮助我们实现复杂的映射关系。想象一下,一个典型的业务场景:一个电商系统中的订单表,其字段包括订单ID、用户ID、商品ID、订单金额等。然而,在业务逻辑层,我们可能需要将订单信息与对应的用户信息和商品信息结合起来,以便于进行后续的业务处理。如果直接使用 MyBatis 的基本映射功能,我们只能获取到订单的基本信息,而无法获取到与之关联的用户和商品信息。这时,ResultMap 就显得尤为重要。
在 MyBatis 中,ResultMap 是核心概念之一,用于定义复杂映射关系的关键功能,它定义了 SQL 查询结果与 Java 对象之间的映射关系。它允许开发者将数据库表中的字段映射到实体类的属性上,同时也可以实现多表联合查询,将多个表的数据映射到一个实体类中。通过 ResultMap,我们可以轻松地实现数据模型与数据库表结构之间的映射,从而简化了数据访问层的开发。
二、基本配置
在开发过程中,我们经常会遇到数据库表与实体类(Entity)结构不一致的情况。例如,一个数据库表可能包含多个字段,而实体类只包含其中的一部分。在这种情况下,如果直接使用 MyBatis 的自动映射功能,可能会导致数据丢失或错误。映射关系是 ResultMap 的核心概念,它定义了数据库表字段与 Java 对象属性之间的对应关系。
| 标签 | 映射类型 | 简要说明 |
|---|---|---|
| id | 主键映射 | 将数据库中的主键字段映射到Java对象的属性上,用于唯一标识记录 |
| result | 普通属性映射 | 将数据库中的普通字段映射到Java对象的属性上 |
| association | 关联映射 | 将查询结果中的关联对象映射到Java对象的属性上 |
| collection | 集合映射 | 将查询结果中的集合对象映射到Java对象的属性上 |
| select | 查询语句 | 定义查询操作,指定查询结果映射规则 |
| resultMap | 映射配置 | 定义查询结果的映射规则,包括字段映射、关联映射和集合映射 |
2.1 resultMap:结果映射
ResultMap 是 MyBatis 框架中用于将数据库查询结果映射为 Java 对象的一种配置,它定义了如何将查询结果集中的列映射到 Java 对象的属性上。通过 resultMap,我们可以自定义字段与实体类属性的映射关系,从而实现灵活的数据转换。
xml
<resultMap id="BaseResultMap" type="com.lilith.entity.User">
<id column="id" property="id" />
<result column="user_name" property="userName" />
<result column="real_name" property="realName" />
<result column="sex" property="sex" />
</resultMap>
一个标准的 resultMap 配置通常包含以下核心属性:
| 属性 | 是否必须 | 简要说明 |
|---|---|---|
| id | ✅ | 当前命名空间中的唯一标识符 |
| type | ✅ | 指定映射的目标 Java 类的全限定名或别名,指定 MyBatis 将查询结果封装成哪种类型的对象 |
| extends | 用于继承其他resultMap,减少重复代码并提高代码的可维护性 | |
| autoMapping | 是否启用自动映射功能。 如果开启(默认情况),自动将结果集中与 Java 对象属性同名(或符合驼峰命名规则)的列进行映射 若设置为 false,则关闭自动映射,必须显式配置所有字段的映射关系 |
💡 根据 MyBatis 的 DTD 约束,resultMap 元素内的子元素必须按照特定顺序排列:constructor ➡️ id ➡️ result ➡️ association ➡️ collection ➡️ discriminator。
2.2 id:主键映射
id 标签是 resultMap 中的一个重要属性,用于指定实体类的主键字段,确保每次操作都能准确找到对应的实体对象。在查询数据时,MyBatis 会将查询结果中的主键值与实体类的主键字段进行映射。id 属性的配置对于正确实现结果映射至关重要,MyBatis 会用其值作为对象标识,提升缓存效率和对象比较准确性。
xml
<id property="id" column="user_id" javaType="int" jdbcType="INTEGER" />
| 属性 | 是否必须 | 简要说明 |
|---|---|---|
| property | ✅ | 指定实体类中对应的主键属性 |
| column | ✅ | 指定数据库表中的主键字段 |
| javaType | 指定Java对象属性的数据类型 | |
| jdbcType | 指定SQL语句中占位符的JDBC类型 | |
| typeHandler | 实现Java类型与JDBC类型之间的自定义转换逻辑 |
2.3 result:普通字段映射
ResultMap 是一个 XML 元素,通常包含了一个或多个 Result 元素,每个 Result 元素用于指定实体类中的非主键字段与数据库表字段的映射关系。
xml
<result property="name" column="user_name" javaType="String" jdbcType="VARCHAR" />
| 属性 | 是否必须 | 简要说明 |
|---|---|---|
| column | ✅ | 对应数据库中的字段名。如果 SQL 中使用了别名,这里需填写别名 |
| property | ✅ | 对应 Java 实体类中的属性名 |
| javaType | 一个 Java 类的全限定名,或一个类型别名 | |
| jdbcType | 指定数据库列类型,避免类型转换错误(如 NULL 值处理) | |
| typeHandler | 指定类型处理器,用于处理Java类型与数据库类型之间的转换 |
2.4 association:一对一关联
一对一关联是指一个实体类对应数据库中的一张表,而另一个实体类与第一个实体类存在一对一的关系。在 MyBatis 中,可以使用 ResultMap 的子标签 association 来实现一对一映射,允许我们在查询一个对象时,同时获取其关联的对象。
xml
<association property="address" javaType="Address" column="address_id" select="selectAddressById" />
| 属性 | 是否必须 | 简要说明 |
|---|---|---|
| property | ✅ | 指定映射到实体类中的对象属性名称 |
| column | ✅ | 指定数据库表中对应的字段名或列别名,该字段的值将作为参数传递给嵌套查询 |
| javaType | 指定该属性对应的 Java 类型 | |
| jdbcType | ||
| select | 指定另一个映射查询的 ID,MyBatis 会额外执行该 SQL 语句获取关联对象的结果 | |
| resultMap | 指定关联实体的映射配置 | |
| typeHandler | ||
| notNullColumn | 如果设置了该属性,只有当指定列的值为非 NULL 时才会执行关联查询,用于优化查询效率 | |
| columnPrefix | 当关联表的列名与主表的列名冲突时,可以为关联表的列名添加前缀以区分 | |
| resultSet | ||
| foreignColumn | ||
| autoMapping | 指定是否启用自动映射。默认为true,表示自动映射列到Java对象的属性忽略大小写 | |
| fetchType | 控制关联数据的加载方式,可选值为 lazy(延迟加载)或 eager(积极加载) |
2.5 collection:一对多关联
一对多关联是指一个实体类对应数据库中的一张表,而另一个实体类与第一个实体类存在一对多的关系。在 MyBatis 中,可以使用 ResultMap 的子标签 collection 来实现一对多关联,允许我们在查询一个对象时,同时获取其关联的集合对象。
xml
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
| 属性 | 是否必须 | 简要说明 |
|---|---|---|
| property | ✅ | 指定映射到实体类中的对象属性名称 |
| column | ✅ | 指定数据库表中对应的字段名(或列别名),该字段的值将作为参数传递给嵌套查询 |
| ofType | 指定集合中元素的Java类型 | |
| jdbcType | 指定该属性对应的 Java 类型 | |
| select | 指定另一个映射查询的 ID,MyBatis 会额外执行该 SQL 语句获取关联对象的结果 | |
| resultMap | 指定集合中元素的映射配置 | |
| typeHandler | ||
| notNullColumn | 如果设置了该属性,只有当指定列的值为非 NULL 时才会执行关联查询,用于优化查询效率。 | |
| columnPrefix | 当关联表的列名与主表的列名冲突时,可以为关联表的列名添加前缀以区分 | |
| resultSet | ||
| foreignColumn | ||
| autoMapping | 指定是否启用自动映射。默认为true,表示自动映射列到Java对象的属性忽略大小写 | |
| fetchType | 控制关联数据的加载方式,可选值为 lazy(延迟加载)或 eager(积极加载) |
三、基本用法
3.1 简单映射
我们可以使用 resultMap 标签自定义结果集和实体类属性的映射规则,假设有一个 User 类,其属性与数据库中的字段不完全对应,可以这样定义 resultMap:
xml
<!-- 1️⃣、定义 resultMap -->
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="password" column="user_pwd"/>
</resultMap>
<!-- 2️⃣、在查询中引用 resultMap -->
<select id="selectUserById" resultMap="userResultMap" parameterType="int">
SELECT user_id, user_name, user_pwd FROM users WHERE user_id = #{id}
</select>
这样,无论字段名如何不同,MyBatis 都能根据 resultMap 将数据正确赋值到 User 对象上。
3.2 多表查询
有的时候需要查询多张表的数据才可以得到我们想要的结果,为此,可以直接写一个多表关联的SQL进行查询,也可以分步进行多次的查询来拿到我们需要的结果。Mybatis 就提供了对应的配置,可以让我们去更方便的进行相应的查询和对应的结果集处理。
关联映射(一对一)
假设每个用户都有一个详细地址信息,这里的地址信息相对于用户是一对一。在查询用户的同时查询用户的地址,可以在 resultMap 中配置如下:
xml
<resultMap id="addressMap" type="org.dllwh.mybatis.model.Address">
<id property="id" column="addr_id"/>
<result property="street" column="street"/>
<result property="city" column="city"/>
<result property="zipCode" column="zipCode"/>
</resultMap>
<resultMap id="userWithAddressMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="userName"/>
<result property="nickName" column="nickName"/>
<!-- 一对一关联:使用 association 标签 -->
<association property="address" resultMap="addressMap" />
</resultMap>
<select id="selectUserWithAddress" resultMap="userWithAddressMap" parameterType="int">
select suu.*, a.* from sys_upms_user suu
left join address a on suu.id = a.user_id
where id = #{userId}
</select>
如果有需要多表查询的需求,也可以选择用多次查询的方式来查询出想要的数据,Mybatis 也提供了对应的配置。例如获取查询用户时,还需查询出该用户所具有的地址信息。我们可以选择先查询User表查询用户信息,然后在去查询关联的地址信息。
xml
<!--
select属性:指定用哪个查询来查询当前属性的数据
column属性:设置当前结果集中哪列的数据作为select属性指定的查询方法需要参数
-->
<resultMap id="userWithAddressMap" type="User">
<id property="id" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="password" column="user_pwd"/>
<!-- 关联映射,通过 user_id 关联地址 -->
<association property="address" column="id" select="selectAddressByUserId"/>
</resultMap>
<!-- 1️⃣、根据userId查询用户 -->
<select id="selectUserWithAddress" resultMap="userWithAddressMap" parameterType="int">
SELECT user_id, user_name, user_pwd FROM users WHERE user_id = #{id}
</select>
<!-- 2️⃣、根据userId查询所具有的地址信息 -->
<!-- 定义查询地址的 SQL -->
<select id="selectAddressByUserId" resultType="Address" parameterType="int">
SELECT addr_id AS id, street, city, zip_code AS zipCode FROM address WHERE user_id = #{userId}
</select>
集合映射(一对多)
假设每个用户可以有多个角色,两个实体之间是一对多的关系。例如在查询用户的同时,还需要该用户所具有的角色信息,可以在 resultMap 中配置如下:
xml
<!--定义User基本属性映射规则-->
<resultMap id="userMap" type="org.dllwh.mybatis.model.SysUser">
<id property="id" column="id"/>
<result property="username" column="username" />
<result property="age" column="age" />
<result property="address" column="address" />
<collection property="roles" resultMap="userRoleMap"/>
</resultMap>
<resultMap id="userRoleMap" type="org.dllwh.mybatis.model.SysRole" >
<id property="id" column="roleId"/>
<result property="roleName" column="roleName"/>
<result property="roleCode" column="roleCode"/>
</resultMap>
<select id="getUserLst" resultMap="userMap">
SELECT suu.*, sur.*
from sys_upms_user suu
LEFT JOIN sys_upms_user_role suur on suur.userId = suu.id
LEFT JOIN sys_upms_role sur on suur.roleId = sur.id
</select>

3.3 类型处理器
在 MyBatis 框架中,结果映射(Result Mapping)是核心功能之一,它负责将数据库查询结果映射到 Java 对象的属性上。类型处理器(Type Handler)是 MyBatis 提供的一种机制,用于处理 Java 类型与数据库类型之间的转换。类型处理器的工作原理是,当MyBatis在执行查询操作时,会根据SQL语句的结果集类型,调用相应的类型处理器进行数据转换。类型处理器内部定义了转换规则,将数据库中的数据类型转换为Java对象中的属性类型。
java
public interface TypeHandler<T> {
// 将数据库类型转换为Java类型
T fromDatabaseType(Object databaseValue);
// 将Java类型转换为数据库类型
Object toDatabaseType(T javaObject);
}
在 MyBatis 配置文件中,类型处理器与数据库类型映射是通过定义 typeHandler 属性来实现。例如,将 VARCHAR 类型的数据库字段映射到 Java 对象的 String 属性:
xml
<resultMap id="userResultMap" type="User">
<result column="name" property="name" typeHandler="com.example.MyCustomTypeHandler"/>
</resultMap>
自定义类型处理器可以满足特定业务需求,以下是一个简单的自定义类型处理器示例:
java
public class MyCustomTypeHandler implements TypeHandler<String> {
@Override
public String fromDatabaseType(Object databaseValue) {
// 将数据库类型转换为Java类型
return (String) databaseValue;
}
@Override
public Object toDatabaseType(String javaObject) {
// 将Java类型转换为数据库类型
return javaObject;
}
}
四、总结
MyBatis 的 resultMap 提供了强大而灵活的机制,使得开发者能够精准地控制 SQL 查询结果与 Java 对象之间的映射关系。无论是简单的单表查询还是复杂的多表联合查询,通过合理设计 resultMap,都能大幅提高数据访问层代码的可读性、可维护性和性能。这种灵活性正是 MyBatis 被广泛使用的重要原因之一,也为开发者在实际项目中处理复杂数据模型提供了极大便利。
