Appearance
结果映射(resultMap)
resultMap 是 MyBatis 最强大也最复杂的特性之一,用于解决数据库列名与 Java 属性名不一致、关联查询、嵌套映射等问题。
一、为什么需要 resultMap?
问题:数据库列名和 Java 属性名不一致
数据库:user_name, user_age
Java: userName, userAge
直接用 resultType 会映射失败,userName 为 nullxml
<!-- 方式一:开启驼峰映射(简单场景) -->
<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 |
