第11章 MyBatis关联映射
深入了解 MyBatis 中的关联映射机制与实现方式
代码演示按钮 💻 查看完整代码 - 在线IDE体验
🎯 关联映射概述
关联映射是MyBatis中处理对象间关系的重要机制,用于解决数据库表之间的关联关系在对象模型中的映射问题。
关联映射的作用
- 对象关系映射:将数据库表间的关联关系映射为对象间的关联关系
- 数据完整性:保证关联数据的一致性和完整性
- 查询优化:通过合理的关联查询减少数据库访问次数
- 代码简化:避免手动处理复杂的关联查询逻辑
关联映射类型
| 映射类型 | 标签 | 描述 | 应用场景 |
|---|---|---|---|
| 一对一 | 一个对象关联另一个对象 | 用户-用户详情、订单-支付信息 | |
| 一对多 | 一个对象关联多个对象 | 用户-订单、部门-员工 | |
| 多对多 | 多个对象相互关联 | 用户-角色、学生-课程 |
🔧 一对一关联映射
一对一关联表示两个实体之间存在唯一对应关系,使用
2.1 实体类设计
// 用户实体
public class User {
private Long id;
private String username;
private String email;
private Date createTime;
private UserProfile profile; // 一对一关联
// getter/setter方法...
}
// 用户详细信息实体
public class UserProfile {
private Long id;
private Long userId;
private String realName;
private String phone;
private String address;
// getter/setter方法...
}
2.2 ResultMap配置
<!-- 一对一关联映射 -->
<resultMap id="UserWithProfileMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="email" property="email"/>
<result column="create_time" property="createTime"/>
<!-- 一对一关联配置 -->
<association property="profile" javaType="UserProfile">
<id column="profile_id" property="id"/>
<result column="user_id" property="userId"/>
<result column="real_name" property="realName"/>
<result column="phone" property="phone"/>
<result column="address" property="address"/>
</association>
</resultMap>
2.3 查询语句
<select id="selectUsersWithProfile" resultMap="UserWithProfileMap">
SELECT u.id, u.username, u.email, u.create_time,
p.id as profile_id, p.user_id, p.real_name, p.phone, p.address
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id
</select>
3. 一对多关联映射
一对多关联表示一个实体对应多个相关实体,使用
3.1 实体类设计
// 用户实体(包含订单列表)
public class User {
private Long id;
private String username;
private String email;
private List<Order> orders; // 一对多关联
// getter/setter方法...
}
// 订单实体
public class Order {
private Long id;
private String orderNo;
private Long userId;
private BigDecimal amount;
private String status;
// getter/setter方法...
}
3.2 ResultMap配置
<!-- 一对多关联映射 -->
<resultMap id="UserWithOrdersMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="email" property="email"/>
<!-- 一对多关联配置 -->
<collection property="orders" ofType="Order">
<id column="order_id" property="id"/>
<result column="order_no" property="orderNo"/>
<result column="amount" property="amount"/>
<result column="status" property="status"/>
</collection>
</resultMap>
3.3 查询语句
<select id="selectUsersWithOrders" resultMap="UserWithOrdersMap">
SELECT u.id, u.username, u.email,
o.id as order_id, o.order_no, o.amount, o.status
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
ORDER BY u.id, o.id
</select>
4. 多对多关联映射
多对多关联通过中间表实现,表示两个实体之间的多对多关系。
4.1 实体类设计
// 用户实体(包含角色列表)
public class User {
private Long id;
private String username;
private String email;
private List<Role> roles; // 多对多关联
// getter/setter方法...
}
// 角色实体
public class Role {
private Long id;
private String roleName;
private String roleCode;
private String description;
private List<User> users; // 反向关联
// getter/setter方法...
}
4.2 ResultMap配置
<!-- 多对多关联映射 -->
<resultMap id="UserWithRolesMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="email" property="email"/>
<!-- 多对多关联配置 -->
<collection property="roles" ofType="Role">
<id column="role_id" property="id"/>
<result column="role_name" property="roleName"/>
<result column="role_code" property="roleCode"/>
<result column="role_description" property="description"/>
</collection>
</resultMap>
4.3 查询语句
<select id="selectUsersWithRoles" resultMap="UserWithRolesMap">
SELECT u.id, u.username, u.email,
r.id as role_id, r.role_name, r.role_code, r.description as role_description
FROM users u
LEFT JOIN user_roles ur ON u.id = ur.user_id
LEFT JOIN roles r ON ur.role_id = r.id
ORDER BY u.id, r.id
</select>
5. 嵌套查询 vs 嵌套结果
5.1 嵌套结果(推荐)
优点:只执行一次SQL查询性能较好,减少数据库访问适合数据量不大的关联查询
5.2 嵌套查询
<!-- 嵌套查询方式 -->
<resultMap id="UserWithProfileNested" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<!-- 嵌套查询 -->
<association property="profile" javaType="UserProfile"
column="id" select="selectProfileByUserId"/>
</resultMap>
<select id="selectProfileByUserId" resultType="UserProfile">
SELECT * FROM user_profiles WHERE user_id = #{id}
</select>
注意:嵌套查询可能导致N+1问题,即查询N个用户时会执行N+1次SQL语句。
6. 延迟加载
延迟加载可以提高性能,只在需要时才加载关联数据。
6.1 全局配置
<!-- mybatis-config.xml -->
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 关闭积极加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
6.2 局部配置
<!-- 在association或collection中配置 -->
<association property="profile" javaType="UserProfile"
column="id" select="selectProfileByUserId"
fetchType="lazy"/>
7. 高级映射技巧
7.1 ResultMap继承
<!-- 基础映射 -->
<resultMap id="BaseUserMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="email" property="email"/>
</resultMap>
<!-- 继承基础映射 -->
<resultMap id="UserWithProfileMap" type="User" extends="BaseUserMap">
<association property="profile" javaType="UserProfile">
<!-- 关联映射配置 -->
</association>
</resultMap>
7.2 复杂条件关联
<!-- 使用多个列作为关联条件 -->
<association property="profile" javaType="UserProfile"
column="{userId=id,status=status}"
select="selectProfileByUserIdAndStatus"/>
8. 性能优化建议
8.1 查询优化合理使用JOIN:优先使用嵌套结果而非嵌套查询控制数据量:避免一次性加载过多关联数据索引优化:为关联字段建立适当的索引分页处理:对大量数据进行分页查询
8.2 内存优化延迟加载:合理使用延迟加载减少内存占用结果缓存:利用MyBatis缓存机制对象复用:避免创建不必要的对象
9. 常见问题与解决方案
9.1 N+1查询问题
问题:使用嵌套查询时,查询N个主对象会执行N+1次SQL。解决方案:使用嵌套结果代替嵌套查询合理设计SQL语句,使用JOIN查询启用批量查询优化
9.2 循环依赖问题
问题:双向关联可能导致无限循环。解决方案:避免在toString()方法中包含关联对象使用@JsonIgnore注解避免JSON序列化循环合理设计对象关系,避免不必要的双向关联
9.3 数据重复问题
问题:一对多查询可能导致主对象数据重复。解决方案:正确配置ResultMap的id标签使用DISTINCT关键字合理设计查询语句的ORDER BY子句
10. 实践练习
练习任务基础练习:实现用户与用户详情的一对一关联查询进阶练习:实现用户与订单的一对多关联查询高级练习:实现用户与角色的多对多关联查询综合练习:实现包含所有关联关系的复合查询性能优化:对比嵌套查询和嵌套结果的性能差异
11. 运行示例程序
运行步骤:确保MySQL数据库已安装并运行执行src/main/resources/init.sql创建数据库和表修改db.properties中的数据库连接信息运行AssociationDemo主程序观察不同类型关联查询的执行结果运行测试类验证各种关联映射功能
🚀 运行关联映射演示