Skip to content

结果映射(resultMap)

  resultMap 是 MyBatis 最强大也最复杂的特性之一,用于解决数据库列名与 Java 属性名不一致、关联查询、嵌套映射等问题。

一、为什么需要 resultMap?

问题:数据库列名和 Java 属性名不一致

数据库:user_name, user_age
Java:   userName,  userAge

直接用 resultType 会映射失败,userName 为 null
xml
<!-- 方式一:开启驼峰映射(简单场景) -->
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

<!-- 方式二:SQL 别名(字段少时可用) -->
<select id="selectAll" resultType="User">
    SELECT user_name AS userName, user_age AS userAge FROM user
</select>

<!-- 方式三:resultMap(字段多或复杂映射时) -->
<resultMap id="userMap" type="User">
    <result property="userName" column="user_name"/>
    <result property="userAge" column="user_age"/>
</resultMap>

二、resultMap 基本元素

xml
<resultMap id="唯一标识" type="映射的实体类">
    <!-- 主键字段 -->
    <id property="实体类属性" column="数据库列名" javaType="Java类型" jdbcType="JDBC类型"/>

    <!-- 普通字段 -->
    <result property="实体类属性" column="数据库列名" javaType="Java类型" jdbcType="JDBC类型"/>

    <!-- 构造器注入 -->
    <constructor>
        <idArg column="id" javaType="long"/>
        <arg column="name" javaType="String"/>
    </constructor>

    <!-- 一对一关联 -->
    <association property="属性" javaType="实体类"/>

    <!-- 一对多关联 -->
    <collection property="属性" ofType="集合元素类型"/>

    <!-- 鉴别器 -->
    <discriminator javaType="int" column="类型列">
        <case value="1" resultMap="子映射1"/>
        <case value="2" resultMap="子映射2"/>
    </discriminator>
</resultMap>

三、一对一关联(association)

场景: 一个订单对应一个用户。

java
// 实体类
public class Order {
    private Long id;
    private String orderNo;
    private BigDecimal amount;
    private User user;  // 关联用户
}

public class User {
    private Long id;
    private String name;
    private String email;
}

3.1 嵌套结果映射(推荐,一条 SQL 搞定)

xml
<resultMap id="orderMap" type="Order">
    <id property="id" column="id"/>
    <result property="orderNo" column="order_no"/>
    <result property="amount" column="amount"/>

    <!-- 一对一关联 -->
    <association property="user" javaType="User">
        <id property="id" column="user_id"/>
        <result property="name" column="user_name"/>
        <result property="email" column="user_email"/>
    </association>
</resultMap>

<select id="selectOrderWithUser" resultMap="orderMap">
    SELECT
        o.id, o.order_no, o.amount,
        u.id AS user_id, u.name AS user_name, u.email AS user_email
    FROM t_order o
    LEFT JOIN t_user u ON o.user_id = u.id
    WHERE o.id = #{id}
</select>

3.2 嵌套查询(N+1 问题,不推荐)

xml
<resultMap id="orderMapNested" type="Order">
    <id property="id" column="id"/>
    <result property="orderNo" column="order_no"/>
    <result property="amount" column="amount"/>

    <!-- 嵌套查询:根据 user_id 再查一次 -->
    <association property="user" column="user_id"
                 javaType="User" select="selectUserById"/>
</resultMap>

<select id="selectOrder" resultMap="orderMapNested">
    SELECT * FROM t_order WHERE id = #{id}
</select>

<select id="selectUserById" resultType="User">
    SELECT * FROM t_user WHERE id = #{id}
</select>

四、一对多关联(collection)

场景: 一个用户对应多个订单。

java
public class User {
    private Long id;
    private String name;
    private List<Order> orders;  // 多个订单
}

4.1 嵌套结果映射(推荐)

xml
<resultMap id="userWithOrdersMap" type="User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>

    <!-- 一对多:ofType 指定集合中元素的类型 -->
    <collection property="orders" ofType="Order">
        <id property="id" column="order_id"/>
        <result property="orderNo" column="order_no"/>
        <result property="amount" column="amount"/>
    </collection>
</resultMap>

<select id="selectUserWithOrders" resultMap="userWithOrdersMap">
    SELECT
        u.id, u.name,
        o.id AS order_id, o.order_no, o.amount
    FROM t_user u
    LEFT JOIN t_order o ON u.id = o.user_id
    WHERE u.id = #{id}
</select>

4.2 嵌套查询

xml
<resultMap id="userMapNested" type="User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>

    <collection property="orders" column="id"
                ofType="Order" select="selectOrdersByUserId"/>
</resultMap>

<select id="selectUser" resultMap="userMapNested">
    SELECT * FROM t_user WHERE id = #{id}
</select>

<select id="selectOrdersByUserId" resultType="Order">
    SELECT * FROM t_order WHERE user_id = #{userId}
</select>

五、多对多关联

场景: 一个用户有多个角色,一个角色有多个用户。

java
public class User {
    private Long id;
    private String name;
    private List<Role> roles;
}

public class Role {
    private Long id;
    private String name;
}
xml
<resultMap id="userWithRolesMap" type="User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>

    <collection property="roles" ofType="Role">
        <id property="id" column="role_id"/>
        <result property="name" column="role_name"/>
    </collection>
</resultMap>

<select id="selectUserWithRoles" resultMap="userWithRolesMap">
    SELECT
        u.id, u.name,
        r.id AS role_id, r.name AS role_name
    FROM t_user u
    LEFT JOIN t_user_role ur ON u.id = ur.user_id
    LEFT JOIN t_role r ON ur.role_id = r.id
    WHERE u.id = #{id}
</select>

六、鉴别器(discriminator)

作用:根据某列的值,选择不同的 resultMap 进行映射

场景:车辆表中有卡车和轿车,根据不同 type 映射到不同子类
java
public class Vehicle {
    private Long id;
    private String type;
}

public class Car extends Vehicle {
    private Integer seats;
}

public class Truck extends Vehicle {
    private Double loadCapacity;
}
xml
<resultMap id="vehicleMap" type="Vehicle">
    <id property="id" column="id"/>
    <result property="type" column="type"/>

    <!-- 鉴别器 -->
    <discriminator javaType="string" column="type">
        <case value="car" resultMap="carMap"/>
        <case value="truck" resultMap="truckMap"/>
    </discriminator>
</resultMap>

<resultMap id="carMap" type="Car" extends="vehicleMap">
    <result property="seats" column="seats"/>
</resultMap>

<resultMap id="truckMap" type="Truck" extends="vehicleMap">
    <result property="loadCapacity" column="load_capacity"/>
</resultMap>

七、自动映射

自动映射的三种级别(autoMappingBehavior):

  NONE          → 关闭自动映射
  PARTIAL(默认) → 只映射不嵌套的、没有定义嵌套映射的
  FULL          → 自动映射所有字段(包括嵌套的)
xml
<settings>
    <!-- 自动映射行为 -->
    <setting name="autoMappingBehavior" value="PARTIAL"/>
    <!-- 映射为 null 时是否设置值 -->
    <setting name="callSettersOnNulls" value="false"/>
</settings>
xml
<!-- 单个 resultMap 也可以控制自动映射 -->
<resultMap id="userMap" type="User" autoMapping="true">
    <id property="id" column="id"/>
    <!-- 其他字段自动映射 -->
</resultMap>

八、使用 @ResultMap 注解

java
// 引用 XML 中定义的 resultMap
@Select("SELECT * FROM t_order WHERE user_id = #{userId}")
@ResultMap("orderMap")
List<Order> selectByUserId(Long userId);

// 纯注解方式
@Results(id = "userMap", value = {
    @Result(property = "id", column = "id", id = true),
    @Result(property = "userName", column = "user_name"),
    @Result(property = "userAge", column = "user_age"),
    @Result(property = "orders", column = "id",
            many = @Many(select = "selectOrdersByUserId"))
})
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);

九、延迟加载

延迟加载:关联查询时,先查主表,需要关联数据时才查子表

配置:
  lazyLoadingEnabled=true     → 开启延迟加载
  aggressiveLazyLoading=false  → 按需加载(不是加载所有懒加载属性)
xml
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
    <!-- 触发延迟加载的方法:equals, clone, hashCode, toString -->
    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
xml
<!-- 在 association 或 collection 中指定 fetchType 覆盖全局配置 -->
<association property="user" column="user_id"
             javaType="User" select="selectUserById"
             fetchType="lazy"/>   <!-- 延迟加载 -->

<association property="user" column="user_id"
             javaType="User" select="selectUserById"
             fetchType="eager"/>  <!-- 立即加载 -->

十、速记总结

resultMap 核心元素:

  id           → 主键映射
  result       → 普通字段映射
  constructor  → 构造器注入
  association  → 一对一(javaType)
  collection   → 一对多(ofType)
  discriminator → 鉴别器(根据字段值选择不同映射)

关联查询两种方式:
  嵌套结果映射(推荐)  → 一条 SQL 查所有,用 resultMap 映射
  嵌套查询(N+1 问题)  → 分多次查询,需要延迟加载优化

速记:
  id 主键,result 普通,
  association 一对一,collection 一对多,
  嵌套结果一条 SQL,嵌套查询 N+1 要避免。

十一、面试要点

问题答案要点
resultMap 和 resultType 区别?resultType 简单映射,resultMap 自定义映射
association 和 collection?association 一对一,collection 一对多
如何解决 N+1 问题?用嵌套结果映射(一条 JOIN SQL)代替嵌套查询
延迟加载怎么配置?lazyLoadingEnabled=true