第08章 结果映射
学习 ResultMap 的使用和复杂结果集的映射处理
学习进度:32% 代码演示按钮 💻 查看完整代码 - 在线IDE体验
🎯 学习目标
📚
理解核心概念
掌握本章的核心概念和基本原理
🔧
掌握实践技能
学会相关的配置方法和实践技巧
💡
了解应用场景
明确在实际项目中的应用场景和最佳实践
学习目标
- 深入理解MyBatis结果映射的工作原理
- 掌握基本结果映射和自定义结果映射
- 学会配置一对一和一对多关联映射
- 理解嵌套查询和嵌套结果的区别
- 掌握延迟加载的配置和使用
- 学会处理复杂的多层嵌套映射
结果映射概述
结果映射是MyBatis最强大的特性之一,它负责将SQL查询结果转换为Java对象。MyBatis提供了灵活的映射机制,支持简单类型映射、复杂对象映射、关联对象映射等多种场景。
结果映射的类型
- 基本映射:直接使用resultType
- 自定义映射:使用resultMap定义复杂映射
- 关联映射:处理对象之间的关联关系
- 集合映射:处理一对多的集合关系
基本结果映射
最简单的结果映射方式,直接使用resultType指定返回类型。
简单类型映射
<!-- 返回基本类型 -->
<select id="countUsers" resultType="long">
SELECT COUNT(*) FROM users
</select>
<!-- 返回字符串 -->
<select id="getUserName" resultType="string">
SELECT name FROM users WHERE id = #{id}
</select>
对象类型映射
<!-- 返回单个对象 -->
<select id="selectUserById" resultType="User">
SELECT id, name, age, email FROM users WHERE id = #{id}
</select>
<!-- 返回对象列表 -->
<select id="selectAllUsers" resultType="User">
SELECT id, name, age, email FROM users
</select>
自动映射规则
- 列名与属性名完全匹配
- 开启驼峰命名转换(mapUnderscoreToCamelCase=true)
- 类型自动转换(String ↔ 数值类型等)
自定义结果映射
当自动映射无法满足需求时,可以使用resultMap定义自定义的结果映射。
基本ResultMap
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<result property="age" column="user_age"/>
<result property="email" column="user_email"/>
</resultMap>
<select id="selectUsersWithCustomMapping" resultMap="userResultMap">
SELECT id as user_id, name as user_name,
age as user_age, email as user_email
FROM users
</select>
ResultMap元素说明
- id:主键映射,用于性能优化
- result:普通字段映射
- association:一对一关联映射
- collection:一对多集合映射
- discriminator:鉴别器,根据条件选择映射
一对一关联映射
使用association标签处理一对一的关联关系。
嵌套查询方式
<resultMap id="userWithProfileMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
<association property="profile" column="id"
select="com.example.mybatis.mapper.UserProfileMapper.selectProfileByUserId"/>
</resultMap>
<select id="selectUsersWithProfile" resultMap="userWithProfileMap">
SELECT id, name, age, email FROM users
</select>
嵌套结果方式
<resultMap id="userWithProfileNestedMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
<association property="profile" javaType="UserProfile">
<id property="id" column="profile_id"/>
<result property="userId" column="user_id"/>
<result property="phone" column="phone"/>
<result property="address" column="address"/>
<result property="birthday" column="birthday"/>
<result property="avatar" column="avatar"/>
</association>
</resultMap>
<select id="selectUsersWithProfileNested" resultMap="userWithProfileNestedMap">
SELECT u.id, u.name, u.age, u.email,
p.id as profile_id, p.user_id, p.phone,
p.address, p.birthday, p.avatar
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id
</select>
两种方式的比较
| 特性 | 嵌套查询 | 嵌套结果 |
|---|---|---|
| SQL数量 | 多个SQL(N+1问题) | 单个SQL |
| 性能 | 可能较慢 | 通常更快 |
| 延迟加载 | 支持 | 不支持 |
| 配置复杂度 | 简单 | 相对复杂 |
一对多集合映射
使用collection标签处理一对多的集合关系。
嵌套查询方式
<resultMap id="userWithOrdersMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
<collection property="orders" column="id"
select="com.example.mybatis.mapper.OrderMapper.selectOrdersByUserId"/>
</resultMap>
<select id="selectUsersWithOrders" resultMap="userWithOrdersMap">
SELECT id, name, age, email FROM users
</select>
嵌套结果方式
<resultMap id="userWithOrdersNestedMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
<collection property="orders" ofType="Order">
<id property="id" column="order_id"/>
<result property="userId" column="order_user_id"/>
<result property="orderNo" column="order_no"/>
<result property="amount" column="amount"/>
<result property="status" column="status"/>
<result property="createTime" column="create_time"/>
</collection>
</resultMap>
<select id="selectUsersWithOrdersNested" resultMap="userWithOrdersNestedMap">
SELECT u.id, u.name, u.age, u.email,
o.id as order_id, o.user_id as order_user_id,
o.order_no, o.amount, o.status, o.create_time
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
ORDER BY u.id, o.id
</select>
复杂嵌套映射
在实际项目中,经常需要同时处理多种关联关系。
用户+档案+订单的复杂映射
<resultMap id="userComplexMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
<!-- 一对一关联:用户档案 -->
<association property="profile" javaType="UserProfile">
<id property="id" column="profile_id"/>
<result property="userId" column="user_id"/>
<result property="phone" column="phone"/>
<result property="address" column="address"/>
<result property="birthday" column="birthday"/>
<result property="avatar" column="avatar"/>
</association>
<!-- 一对多关联:用户订单 -->
<collection property="orders" ofType="Order">
<id property="id" column="order_id"/>
<result property="userId" column="order_user_id"/>
<result property="orderNo" column="order_no"/>
<result property="amount" column="amount"/>
<result property="status" column="status"/>
<result property="createTime" column="create_time"/>
</collection>
</resultMap>
<select id="selectUsersWithComplexMapping" resultMap="userComplexMap">
SELECT u.id, u.name, u.age, u.email,
p.id as profile_id, p.user_id, p.phone, p.address, p.birthday, p.avatar,
o.id as order_id, o.user_id as order_user_id, o.order_no,
o.amount, o.status, o.create_time
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id
LEFT JOIN orders o ON u.id = o.user_id
ORDER BY u.id, o.id
</select>
延迟加载
延迟加载是一种性能优化技术,只有在真正访问关联对象时才执行查询。
全局配置
<!-- 在mybatis-config.xml中配置 -->
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 关闭积极的延迟加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
局部配置
<resultMap id="userLazyMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
<!-- 延迟加载用户档案 -->
<association property="profile" column="id" fetchType="lazy"
select="com.example.mybatis.mapper.UserProfileMapper.selectProfileByUserId"/>
<!-- 延迟加载用户订单 -->
<collection property="orders" column="id" fetchType="lazy"
select="com.example.mybatis.mapper.OrderMapper.selectOrdersByUserId"/>
</resultMap>
fetchType属性
- eager:立即加载
- lazy:延迟加载
- 默认值:根据全局配置决定
高级映射技巧
1. 使用构造方法映射
<resultMap id="userConstructorMap" type="User">
<constructor>
<idArg column="id" javaType="long"/>
<arg column="name" javaType="string"/>
<arg column="age" javaType="int"/>
<arg column="email" javaType="string"/>
</constructor>
</resultMap>
2. 鉴别器映射
<resultMap id="userDiscriminatorMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<discriminator javaType="string" column="user_type">
<case value="VIP" resultMap="vipUserMap"/>
<case value="NORMAL" resultMap="normalUserMap"/>
</discriminator>
</resultMap>
3. 继承映射
<resultMap id="baseUserMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
</resultMap>
<resultMap id="extendedUserMap" type="User" extends="baseUserMap">
<association property="profile" column="id"
select="selectProfileByUserId"/>
</resultMap>
性能优化建议
1. 选择合适的映射方式
- 简单查询使用resultType
- 复杂映射使用resultMap
- 一对一关系优先使用嵌套结果
- 一对多关系根据数据量选择
2. 避免N+1问题
- 合理使用嵌套结果映射
- 启用延迟加载
- 批量查询优化
- 缓存机制配合
3. 延迟加载最佳实践
- 只对大对象或集合使用延迟加载
- 避免在循环中触发延迟加载
- 注意Session生命周期
- 合理设置fetchType
4. 内存使用优化
- 避免加载不必要的字段
- 合理设计对象结构
- 及时释放大对象
- 使用分页查询
常见问题与解决方案
Q: 延迟加载时出现LazyInitializationException?
A: 确保在Session关闭前访问延迟加载的属性,或者使用OpenSessionInView模式。
Q: 一对多映射时数据重复?
A: 检查resultMap中的id配置是否正确,确保主键映射准确。
Q: 嵌套结果映射性能差?
A: 考虑使用嵌套查询+延迟加载,或者优化SQL查询和索引。
Q: 复杂映射配置错误?
A: 逐步构建映射,先测试简单映射再添加复杂关联。
Q: 类型转换异常?
A: 检查javaType和jdbcType配置,确保类型匹配。
实践练习
练习1:基本映射
创建一个自定义ResultMap,处理列名与属性名不匹配的情况。
练习2:一对一映射
实现用户和用户档案的一对一映射,分别使用嵌套查询和嵌套结果两种方式。
练习3:一对多映射
实现用户和订单的一对多映射,观察数据的组织方式。
练习4:延迟加载
配置延迟加载,观察SQL执行时机和性能差异。
练习5:复杂映射
实现包含用户、档案、订单的三层嵌套映射。
运行示例程序
步骤1:导入项目
将chapter-08-result-mapping.json导入到IntelliJ IDEA中。
步骤2:配置数据库
确保MySQL数据库运行正常,执行init.sql创建测试数据。
步骤3:运行程序
运行ResultMappingDemo.java,观察各种结果映射的执行效果。
步骤4:观察SQL日志
查看控制台输出的SQL语句,理解不同映射方式的查询策略。
步骤5:测试延迟加载
观察延迟加载的触发时机和SQL执行情况。
步骤6:运行测试
执行ResultMappingTest.java中的测试用例,验证各种映射功能。
本章小结
📖 本章小结
本章我们学习了 结果映射 的相关内容,包括:
- 核心概念和基本原理
- 配置方法和实践技巧
- 应用场景和最佳实践
- 常见问题和解决方案
下一章节 🎉
恭喜完成本章学习!
你已经掌握了 结果映射 的核心知识
第09章缓存机制继续学习 MyBatis 的更多特性和应用⏱️预计 30 分钟📊中等难度开始学习 → ← 第07章 📚 课程目录 第09章 →