最有效解法是用JOIN配合resultMap单次查询。需显式JOIN并用别名区分字段,精简resultMap,合理配置延迟加载(需搭配批量机制),对读多写少数据启用二级缓存。

直接用 JOIN 配合 resultMap 做单次查询,是最有效、最常用的解法。N+1 本质是“查完主表再逐条查子表”,只要把关联数据一次性拉回来,问题就消失了。
用 JOIN 替代嵌套 select
别在 <collection> 或 <association> 里写 select="xxx"。改成在 SQL 中显式 JOIN,并在 resultMap 中用别名区分字段:
- 主表字段(如
u.id,u.name)直接映射到主对象属性 - 关联表字段(如
o.id as order_id,o.amount)加别名,避免列名冲突 - 在
<collection>内用<id>和<result>映射子对象字段,MyBatis 会按主键自动分组组装
合理配置延迟加载(慎用)
如果确实不需要每次查出全部关联数据,可开启懒加载,但必须配合批量机制防 N+1:
- 全局启用:
<setting name="lazyLoadingEnabled" value="true"/> - 禁用激进模式:
<setting name="aggressiveLazyLoading" value="false"/> - 在映射中显式声明:
fetchType="lazy",且确保 SqlSession 未关闭 - 注意:单独用 lazy 不解决 N+1,它只是把“立即 N+1”变成“访问时 N+1”,需搭配批量加载或二次优化
精简 resultMap,避免无用映射
每个映射字段都会参与结果解析,冗余字段不仅浪费内存,还拖慢映射过程:
- 只 select 实际需要的列,不写
SELECT * - resultMap 中只定义业务用到的字段,删掉注释掉或已废弃的
<result> - 对集合关联,明确指定
ofType和唯一标识<id>,帮助 MyBatis 正确去重和分组
用二级缓存减少重复查询
对读多写少、变动不频繁的关联数据(如省市区、字典项),开启二级缓存能显著降低数据库压力:
- 在 Mapper XML 顶部加
<cache /> - 设置合适淘汰策略(如
eviction="LRU")和刷新周期(flushInterval="60000") - 注意:缓存的是整个查询结果,不是单个关联对象;更新主表时要配置缓存刷新逻辑


















