🎯 一句话总结
@MapKey 告诉 MyBatis:把查询结果的某一列作为 Map 的 key,整行数据作为 value。
📖 为什么需要 @MapKey?
场景:查询多条记录,想通过某个字段快速查找
假设你有以下数据:
| id | name | |
|---|---|---|
| 1 | 张三 | zhang@example.com |
| 2 | 李四 | li@example.com |
| 3 | 王五 | wang@example.com |
你想通过 id 快速找到对应的用户。
❌ 没有 @MapKey 的情况
Mapper 接口
java
List<Map<String, Object>> queryUsers();
返回结果
java
[
{id=1, name=张三, email=zhang@example.com},
{id=2, name=李四, email=li@example.com},
{id=3, name=王五, email=wang@example.com}
]
使用方式(麻烦)
java
List<Map<String, Object>> list = mapper.queryUsers();
// 想找到 id=2 的用户?需要遍历!
Map<String, Object> target = null;
for (Map<String, Object> row : list) {
if (Integer.valueOf(2).equals(row.get("id"))) {
target = row;
break;
}
}
问题: 每次查找都要遍历整个列表,性能差!
✅ 有 @MapKey 的情况
Mapper 接口
java
@MapKey("id") // ← 指定用 id 作为 key
Map<String, Map<String, Object>> queryUsers();
返回结果
java
{
"1": {id=1, name=张三, email=zhang@example.com},
"2": {id=2, name=李四, email=li@example.com},
"3": {id=3, name=王五, email=wang@example.com}
}
使用方式(简单)
java
Map<String, Map<String, Object>> map = mapper.queryUsers();
// 想找到 id=2 的用户?直接 get!
Map<String, Object> target = map.get("2");
优势: O(1) 时间复杂度,瞬间找到!
🔍 @MapKey 的工作原理
形象比喻
想象你有一堆名片:
没有 @MapKey:
名片堆在一起,想找"张三"的名片?
→ 必须一张张翻,直到找到为止 😫
有 @MapKey:
名片按名字排好序放在抽屉里,想找"张三"?
→ 直接打开"张"字抽屉,瞬间拿到 😊
技术实现
java
@MapKey("id")
Map<String, Map<String, Object>> queryUsers();
MyBatis 会:
- 执行 SQL,得到多行结果
- 遍历每一行
- 取出该行的
id列的值(如 "1"、"2"、"3") - 把这一整行数据作为 value
- 组成
Map<"1", 该行数据>、Map<"2", 该行数据>...
📊 对比总结
| 特性 | 没有 @MapKey | 有 @MapKey |
|---|---|---|
| 返回类型 | List<Map<K,V>> |
Map<String, Map<K,V>> |
| 查找方式 | 遍历列表 | 直接 get(key) |
| 时间复杂度 | O(n) | O(1) |
| 代码可读性 | 差 | 好 |
| 适用场景 | 只需要遍历 | 需要通过某列快速查找 |
🎓 常见误区
误区 1:@MapKey 只能用于 Map 返回值
❌ 错误理解: @MapKey 只能配合 Map 返回类型使用
✅ 正确理解: @MapKey 可以用于任何返回多条记录的查询,包括:
Map<String, Object>- 单条记录Map<String, Map<String, Object>>- 多条记录Map<String, YourDO>- 多条记录映射到对象
误区 2:@MapKey 的值可以是任意字段
❌ 错误用法:
java
@MapKey("non_existent_column") // 这个字段在 SQL 中不存在
✅ 正确用法:
java
@MapKey("id") // 必须是 SELECT 中的列名
误区 3:@MapKey 会自动去重
❌ 如果有重复的 key:
sql
SELECT id, ... FROM table
-- 结果:
-- 1
-- 1 ← 重复!
后面的会覆盖前面的!
✅ 解决方案:
sql
SELECT DISTINCT id, ... FROM table
🚀 最佳实践
1. 明确是否需要快速查找
java
// 只需要遍历 → 不需要 @MapKey
List<User> list = mapper.selectAll();
for (User u : list) { ... }
// 需要通过 ID 查找 → 需要 @MapKey
@MapKey("id")
Map<String, User> map = mapper.selectAll();
User u = map.get("123");
2. 选择合适的 key 字段
java
// ✅ 好的选择:唯一、非空、简短
@MapKey("id")
@MapKey("user_code")
// ❌ 不好的选择:可能重复、可能为空、太长
@MapKey("description")
@MapKey("email") // 邮箱可能很长
3. 配合 Java 8 Stream 使用
java
// 如果 Mapper 返回 List,可以用 Stream 转 Map
List<User> list = mapper.selectAll();
Map<String, User> map = list.stream()
.collect(Collectors.toMap(User::getId, u -> u));
💬 总结
@MapKey 的本质:
把数据库查询结果从"列表"变成"字典",让你可以通过某个字段快速查找记录。
什么时候用:
- ✅ 需要通过某列快速查找
- ✅ 避免多次遍历列表
- ✅ 提高代码可读性
什么时候不用:
- ❌ 只需要遍历所有数据
- ❌ 只有一条记录
- ❌ key 字段不唯一