MyBatis-Plus(简称 MP)作为 MyBatis 的增强工具,在简化 CRUD 操作的同时,也保留了 MyBatis 强大的结果映射能力。ResultMap 是 MyBatis/MyBatis-Plus 中用于将数据库结果集映射到 Java 对象的核心机制,尤其适用于复杂查询、字段与属性名不一致、多表关联、嵌套对象等场景。


一、核心概念

1. ResultMap 是什么?

ResultMap 是 MyBatis 提供的一个强大的结果映射配置,用于定义数据库列(column)与 Java 对象属性(property)之间的映射关系。它比自动映射更灵活、更可控。

2. 为什么需要 ResultMap?

  • 数据库字段名与 Java 属性名不一致(如 user_name → userName)
  • 多表联查,结果需要映射到嵌套对象(如 User 包含 Role)
  • 处理 null 值的特殊逻辑
  • 枚举类型、自定义类型处理器(TypeHandler)
  • 避免 N+1 查询问题(通过 association 和 collection)

3. MyBatis-Plus 与 ResultMap 的关系

  • MP 默认使用自动映射(Auto Mapping),适用于简单场景。
  • 当自动映射无法满足需求时,可手动定义 ResultMap。
  • MP 完全兼容 MyBatis 的 ResultMap 机制。

二、操作步骤(非常详细)

步骤 1:创建实体类(POJO)

// 用户实体
public class User {
    private Long id;
    private String userName;
    private Integer age;
    private String email;
    private Role role; // 嵌套对象
    // getter/setter 省略
}

// 角色实体
public class Role {
    private Long id;
    private String roleName;
    private String description;
    // getter/setter 省略
}

步骤 2:编写 Mapper 接口

@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    // 自定义方法,使用 ResultMap
    List<User> selectUserWithRole(@Param("roleId") Long roleId);
}

步骤 3:编写 XML 映射文件(UserMapper.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.example.mapper.UserMapper">

    <!-- 定义 ResultMap -->
    <resultMap id="UserWithRoleResultMap" type="User">
        <!-- 基本字段映射 -->
        <id property="id" column="user_id" />
        <result property="userName" column="user_name" />
        <result property="age" column="age" />
        <result property="email" column="email" />

        <!-- 嵌套对象映射 -->
        <association property="role" javaType="Role">
            <id property="id" column="role_id" />
            <result property="roleName" column="role_name" />
            <result property="description" column="role_desc" />
        </association>
    </resultMap>

    <!-- 查询语句,使用 resultMap -->
    <select id="selectUserWithRole" resultMap="UserWithRoleResultMap">
        SELECT 
            u.id AS user_id,
            u.user_name,
            u.age,
            u.email,
            r.id AS role_id,
            r.role_name,
            r.description AS role_desc
        FROM user u
        LEFT JOIN role r ON u.role_id = r.id
        <where>
            <if test="roleId != null">
                AND r.id = #{roleId}
            </if>
        </where>
    </select>

</mapper>

步骤 4:在 Service 层调用

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public List<User> getUsersWithRole(Long roleId) {
        return userMapper.selectUserWithRole(roleId);
    }
}

步骤 5:验证结果

调用 getUsersWithRole(1L),返回的 User 列表中每个 User 对象的 role 属性已被正确填充。


三、常见错误与解决方案

错误现象 原因 解决方案
字段未映射,值为 null property 或 column 名称拼写错误 检查 resultMap 中的 property 和 column 是否正确
嵌套对象为 null association 的 column 没有对应数据库字段 确保 SQL 查询返回了嵌套对象所需的所有字段
ResultMap 找不到 resultMap 的 id 不存在或拼写错误 检查 resultMap 的 id 与 select 标签中的 resultMap 属性是否一致
类型转换异常 数据库类型与 Java 类型不匹配 使用 typeHandler 或检查字段类型
N+1 查询性能差 使用了 select 方式嵌套查询 改用 join 查询 + resultMap 关联映射

四、注意事项

  1. 命名规范:

    • 数据库字段建议使用下划线命名(如 user_name)。
    • Java 属性使用驼峰命名(如 userName)。
    • MP 默认开启 mapUnderscoreToCamelCase,可自动映射,但复杂场景仍需 ResultMap。
  2. 主键映射:

    • 使用 <id> 标签映射主键,有助于 MyBatis 优化缓存和结果处理。
  3. 避免循环引用:

    • 如果 User 包含 Role,而 Role 又包含 List<User>,需注意避免无限递归。
  4. XML 文件位置:

    • 确保 UserMapper.xml 在 resources/mapper/ 目录下,并在 application.yml 中配置:
      mybatis-plus:
        mapper-locations: classpath:mapper/*.xml
      

五、使用技巧

1. 重用 ResultMap

可以定义基础 ResultMap,并通过 <extends> 继承:

<resultMap id="BaseUserResultMap" type="User">
    <id property="id" column="user_id" />
    <result property="userName" column="user_name" />
    <result property="age" column="age" />
</resultMap>

<resultMap id="UserWithRoleResultMap" type="User" extends="BaseUserResultMap">
    <association property="role" javaType="Role">
        <id property="id" column="role_id" />
        <result property="roleName" column="role_name" />
    </association>
</resultMap>

2. 使用自动映射 + 手动补充

<resultMap id="PartialResultMap" type="User" autoMapping="true">
    <association property="role" javaType="Role" autoMapping="true">
        <id property="id" column="role_id" />
    </association>
</resultMap>

autoMapping="true" 表示未明确映射的字段尝试自动映射。

3. 处理枚举类型

public enum Status {
    ACTIVE(1), INACTIVE(0);
    private int code;
    // 构造函数、getter
}
<result property="status" column="status" typeHandler="com.example.enums.StatusTypeHandler"/>

六、最佳实践与性能优化

✅ 最佳实践

  1. 优先使用 MP 自动映射:简单场景无需 ResultMap。
  2. 复杂查询使用 ResultMap:多表关联、嵌套对象、字段不一致。
  3. 合理使用 association 和 collection:避免 N+1 查询。
  4. 命名清晰:resultMap 的 id 应具有业务含义,如 UserWithRoleResultMap。
  5. 避免过度嵌套:层级不宜过深,影响可读性和性能。

⚡ 性能优化

  1. 使用 JOIN 而非嵌套 SELECT:

    <!-- ❌ 避免 -->
    <association property="role" column="role_id" 
                 select="selectRoleById" />
    
    <!-- ✅ 推荐 -->
    <association property="role" javaType="Role">
        <id property="id" column="role_id" />
        ...
    </association>
    
  2. 只查询必要字段:避免 SELECT *,减少网络传输。

  3. 合理使用缓存:MyBatis 一级/二级缓存可提升重复查询性能。

  4. 批量处理:使用 collection 映射集合时,可结合 fetchType="lazy" 实现懒加载。


七、总结

场景 推荐方案
简单 CRUD MP 自动映射
字段名不一致 resultMap + result
多表关联 resultMap + association/collection
枚举/复杂类型 resultMap + typeHandler
性能敏感 JOIN + resultMap,避免 N+1

掌握 ResultMap 是深入使用 MyBatis-Plus 的关键技能之一。它赋予你对结果映射的完全控制权,是处理复杂业务查询的利器。