根本原因是字段名冲突导致mybatis按标签误判多行为同一实体——当主子表同名id未加别名区分时,结果集映射触发错误行合并,只保留一个对象实例。

MyBatis 多表联查返回多条记录却只得到一条,根本原因通常不是缓存重写,而是字段名冲突导致的结果集映射异常——尤其当主表与子表存在同名主键(如 id)时,MyBatis 会按 <id></id> 标签定义的列值做“行合并”判断,把不同记录误判为同一实体的多次填充,最终只保留一个对象实例。
为什么不是一级缓存导致的数据覆盖?
一级缓存作用于 SqlSession 生命周期内,只影响“相同 SQL + 相同参数”的重复查询;它不会让一次查询的多行结果彼此覆盖。你执行一条 JOIN 查询返回 5 行,无论缓存是否启用,底层 ResultSet 都是 5 行。真正出问题的是 MyBatis 将这 5 行映射成 Java 对象的过程——它依赖 <id></id> 字段做“是否为同一主实体”的判定。若多张表都选了 id,且未加区分,MyBatis 就会用子表的 id 去匹配主表缓存键,触发错误合并。
关键 Bug 触发条件
- SQL 中多个表 SELECT 出同名字段(尤其是
id、name等通用字段) - ResultMap 中
<id></id>或<result></result>的column属性未指向唯一标识列(比如写成column="id",但结果集中有多个id) - 未使用别名区分来源,也未在 resultMap 中显式绑定别名
- 查询结果集顺序混乱(如 B-A-B),加剧了合并逻辑误判
正确解决方式:从 SQL 到 ResultMap 全链路隔离
不靠清缓存、不靠关闭一级缓存(它不能关,也不该关),而要确保每行数据有唯一可识别的主键投影:
- SQL 中为所有可能重名的字段显式起别名,例如:
u.id AS user_id, r.id AS role_id, p.id AS perm_id - ResultMap 的
<id></id>必须指向主实体唯一标识,如<id column="user_id" property="id"></id> - 子对象或集合字段用
<association></association>或<collection></collection>单独配置,其内部column也要用对应别名,如column="role_id" - 避免使用
SELECT *,只查必需字段,既能提升性能,也能提前暴露重名风险
验证是否修复的小技巧
执行 SQL 查看原始结果集:确认每行都有清晰可区分的主键别名;再调试 MyBatis 日志(开启 log4j.logger.org.apache.ibatis=DEBUG),观察实际映射时读取的 column 名是否与 SQL 别名一致。如果日志中显示反复对同一个 user_id 赋值,说明 <id></id> 配置正确;若出现 role_id 被当作主键处理,则需检查 resultMap 结构嵌套是否错位。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











