MyBatis 一对多与多对一映射详解教程
一、基础概念与场景
- 一对多(One-to-Many)
• 定义:一个父对象包含多个子对象。
例如:一个商品(Goods)对应多个商品详情(GoodsDetail)
• 实体类表现:父类中包含 List<子类>
属性。
// Goods.java
public class Goods {private List<GoodsDetail> goodsDetails; // 一对多关系
}
- 多对一(Many-to-One)
• 定义:多个子对象属于一个父对象。
例如:多个商品详情(GoodsDetail)属于一个商品(Goods)
• 实体类表现:子类中包含父类对象属性。
// GoodsDetail.java
public class GoodsDetail {private Goods goods; // 多对一关系
}
二、XML配置实现
- 一对多映射(父→子)
使用<collection>
标签,核心配置如下:
<!-- GoodsMapper.xml -->
<resultMap id="rmGoods1" type="Goods"><id column="goods_id" property="goodsId"/><!-- 一对多:通过子查询获取商品详情 --><collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id"/>
</resultMap><select id="selectOneToMany" resultMap="rmGoods1">SELECT * FROM t_goods LIMIT 0,10
</select>
• 参数解释:
• select
:指向子查询的命名空间(如 goodsDetail.selectByGoodsId
)
• column
:传递父查询结果中的字段值(如 goods_id
)到子查询
- 多对一映射(子→父)
使用<association>
标签,核心配置如下:
<!-- GoodsDetailMapper.xml -->
<resultMap id="rmGoodsDetail" type="GoodsDetail"><id column="gd_id" property="gdId"/><!-- 多对一:通过父查询获取商品信息 --><association property="goods" select="goods.selectById" column="goods_id"/>
</resultMap><select id="selectManyToOne" resultMap="rmGoodsDetail">SELECT * FROM t_goods_detail LIMIT 0,1
</select>
• 参数解释:
• select
:指向父查询的命名空间(如 goods.selectById
)
• column
:传递当前表的字段值(如 goods_id
)到父查询
三、联表查询优化
- 一对多联表查询(避免N+1问题)
直接通过JOIN查询一次性获取父子数据:
<resultMap id="rmGoodsJoin" type="Goods"><collection property="goodsDetails" ofType="GoodsDetail"><id column="gd_id" property="gdId"/><result column="gd_pic_url" property="gdPicUrl"/></collection>
</resultMap><select id="selectJoin" resultMap="rmGoodsJoin">SELECT g.*, d.* FROM t_goods g LEFT JOIN t_goods_detail d ON g.goods_id = d.goods_id
</select>
• 优点:减少数据库查询次数,提升性能
- 多对一联表查询
<resultMap id="rmDetailJoin" type="GoodsDetail"><association property="goods" javaType="Goods"><id column="goods_id" property="goodsId"/><result column="title" property="title"/></association>
</resultMap><select id="selectDetailWithGoods" resultMap="rmDetailJoin">SELECT d.*, g.title FROM t_goods_detail d INNER JOIN t_goods g ON d.goods_id = g.goods_id
</select>
四、常见问题与解决方案
- N+1查询问题
• 现象:父查询返回N条数据,触发N次子查询,导致性能低下。
• 解决:
• 开启懒加载:在 mybatis-config.xml
中配置:
```xml
<settings><setting name="lazyLoadingEnabled" value="true"/>
</settings>
```
• 使用联表查询(如上述第三节)
- 字段名冲突
• 现象:联表查询时多个表有相同字段名(如id
)。
• 解决:
• 使用别名区分字段:
```sql
SELECT g.id AS goods_id, d.id AS detail_id
```
• 在 <resultMap>
中明确指定映射关系
五、最佳实践总结
场景 | 推荐方案 | 适用场景 |
---|---|---|
数据量小 | 分步查询(<collection> /<association> ) | 字段关联逻辑简单 |
数据量大 | 联表查询 | 需要一次性获取完整数据 |
实时性要求高 | 联表查询 + 缓存 | 高频读取场景 |
更新频繁 | 分步查询 + 懒加载 | 减少无效数据加载 |
学习建议:
- 从简单分步查询入手,理解映射逻辑
- 逐步尝试联表查询,对比性能差异
- 参考官方文档中的
<resultMap>
高级用法 - 通过单元测试验证结果集映射(如
System.out.println(goods.getGoodsDetails().size())
)