← 返回题目列表

MyBatis 查询怎么返回 Map?@MapKey、List<Map> 和插入返回主键怎么用?

简单 第 19 / 24 题 更新于 2026/07/28
返回MapMapKey返回主键useGeneratedKeys

简化版

MyBatis 除了返回实体对象/List,还能返回 Map 形式的结果,常见三种:单行返回 Map<String, Object>——查一行、但不想定义实体类时,返回 Map(key 是列名、value 是列值),适合动态列、临时查询;② 多行返回 List<Map<String,Object>>——每行一个 Map,适合结果结构不固定的多行查询;③ 多行返回 Map<K, V>(用 @MapKey——把「List 结果」转成「以某个字段为 key 的 Map」,比如查一批用户返回 Map<Long, User>(key 是 userId),方便按 id 快速查找,用 @MapKey("id") 指定用哪个字段做 key。另外「插入后拿自增主键」也很常用:用 @Options(useGeneratedKeys=true, keyProperty="id")(或 XML 的 useGeneratedKeys),插入后 MyBatis 会把数据库生成的自增主键回填到实体的 id 字段,不用再查一次。核心:Map 返回适合「不想定义实体/列不固定」的场景,@MapKey 方便按字段建索引,useGeneratedKeys 拿回自增主键。

详细版

几种 Map 返回方式

返回类型说明适合
Map<String,Object>单行 → 列名:列值单行、不想定义实体
List<Map<String,Object>>多行,每行一个 Map多行、列不固定
@MapKey("id") Map<Long,User>多行 → 以字段为 key 的 Map需要按字段快速查找
// ① 单行返回 Map(key=列名)
@Select("SELECT id, name, age FROM user WHERE id=#{id}")
Map<String, Object> selectAsMap(Long id);   // {id=1, name=Tom, age=18}

// ② 多行返回 List<Map>
@Select("SELECT * FROM user")
List<Map<String, Object>> selectAllAsMapList();

// ③ 多行返回 Map<key, entity>,@MapKey 指定 key 字段
@MapKey("id")
@Select("SELECT * FROM user")
Map<Long, User> selectAsUserMap();   // {1=User(1,...), 2=User(2,...)}
// → 之后 map.get(1L) 直接拿到 id=1 的用户

// ④ 插入回填自增主键
@Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
// 调用后:user.getId() 就是数据库生成的自增主键

⚠️ 返回 Map 虽然方便,但要节制使用——它牺牲了「类型安全」和「可读性」:Map<String, Object> 拿值要 map.get("name")(字符串 key 容易写错列名,且 value 是 Object 要强转),IDE 无法补全和检查,重构时也不会自动改。所以能定义实体就定义实体resultType="User"),返回 Map 主要用于:① 临时/一次性查询,不值得建实体;② 列不固定的动态查询(如报表、动态字段);③ 只查一两个聚合值。而 @MapKey 的价值场景是「查出一批数据后要频繁按某字段查找」——比如查出 1000 个用户,后面要反复用 userId 查对应用户,用 Map<Long,User> 就是 O(1) 查找,比在 List 里遍历找(O(n))高效;本质是「查询时顺便建了个索引」。

完整版教学

一、单行返回 Map

先看最简单的「单行返回 Map」:

返回类型写成 Map<String, Object>:
  @Select("SELECT id, name, age FROM user WHERE id=#{id}")
  Map<String, Object> selectAsMap(Long id);

结果:一个 Map,key 是列名、value 是列值
  {id=1, name="Tom", age=18}

什么时候用:
  ① 不想为这个查询定义一个实体类(临时、一次性)
  ② 查询的列不固定(动态 SQL 查出来的列可能变)
  ③ 只是拿几个值,建实体不划算

取值:
  map.get("name")  → 拿 name 列的值(是 Object,可能要强转)

缺点:
  - 字符串 key 容易写错列名("nane" 不报错,返回 null)
  - value 是 Object,用时要强转,没有类型安全
  - IDE 不能补全/检查

单行返回 Map<String, Object>——key 是列名、value 是列值。适合「不想定义实体(临时/一次性)、列不固定、只拿几个值」。缺点是字符串 key 易写错、value 是 Object 要强转、无类型安全、IDE 不能补全。理解「单行返回 Map(key=列名 value=列值)、适合不想定义实体/列不固定、缺点是字符串 key 易错和无类型安全」,就掌握了单行返回 Map。

二、多行返回 List

多行查询也能返回 Map,每行一个:

返回类型 List<Map<String, Object>>:
  @Select("SELECT * FROM user")
  List<Map<String, Object>> selectAllAsMapList();

结果:List 里每个元素是一行的 Map
  [{id=1, name=Tom}, {id=2, name=Jerry}, ...]

什么时候用:
  ① 多行、且列结构不固定(如动态报表:SELECT 的列是动态拼的)
  ② 通用查询工具(不知道具体查什么表、什么列)
  ③ 导出、展示原始数据

对比返回 List<实体>:
  List<User>:类型安全、有实体、推荐(列固定时)
  List<Map>:灵活、无需实体,但无类型安全(列不固定时用)

所以:列固定用 List<实体>,列不固定用 List<Map>

多行返回 List<Map<String,Object>>——每行一个 Map。适合「多行且列结构不固定」(动态报表、通用查询工具、导出原始数据)。对比 List<实体>列固定用 List<实体>(类型安全、推荐),列不固定用 List<Map>(灵活但无类型安全)。理解「多行返回 List每行一个 Map、适合多行列不固定(动态报表)、列固定用 List<实体>列不固定用 List」,就掌握了多行返回 Map。

三、@MapKey:把 List 转成索引 Map

@MapKey 是更有价值的用法——把结果转成「以某字段为 key 的 Map」:

需求:查出一批用户后,要频繁按 userId 查找对应用户
  普通做法:返回 List<User>,然后在 List 里遍历找(O(n),慢)

@MapKey 做法:返回 Map<key, User>,以某字段为 key
  @MapKey("id")   ← 指定用 User 的 id 字段做 Map 的 key
  @Select("SELECT * FROM user")
  Map<Long, User> selectAsUserMap();

  结果:{1=User(1,Tom), 2=User(2,Jerry), ...}
    key 是每个 User 的 id,value 是 User 对象

  之后:map.get(1L) → O(1) 拿到 id=1 的用户(快)

本质:查询时"顺便建了个索引"
  把 List 结构变成"字段→对象"的 Map,方便按字段快速查找

适合:查出数据后要反复按某字段(通常是主键/唯一键)查找
  比在 List 里遍历找高效(O(1) vs O(n))

注意:@MapKey 指定的字段应该是唯一的(否则重复 key 会覆盖)

@MapKey 是更有价值的用法——把查询结果转成「以某字段为 key 的 Map」@MapKey("id") 指定用 id 做 key,返回 Map<Long, User>)。本质是「查询时顺便建了个索引」——之后 map.get(id)O(1) 查找,比在 List 里遍历找(O(n))高效。适合「查出数据后要反复按某字段(主键/唯一键)查找」。注意 @MapKey 指定的字段应唯一(否则重复 key 覆盖)。理解「@MapKey 把 List 结果转成以字段为 key 的 Map(相当于建索引)、map.get(id)O(1)查找比 List 遍历 O(n)快、字段应唯一」,就掌握了 @MapKey 的价值。

四、插入回填自增主键

另一个高频需求「插入后拿自增主键」,用 useGeneratedKeys

需求:插入一条记录后,要拿到数据库生成的自增主键
  (比如插入订单后,要用订单 id 做后续操作)

普通问题:insert 返回的是"影响行数",不是主键
  再查一次拿 id?多余、还可能不准(并发)

useGeneratedKeys 方案:
  @Insert("INSERT INTO user(name) VALUES(#{name})")
  @Options(useGeneratedKeys = true, keyProperty = "id")
  int insert(User user);

  含义:
    useGeneratedKeys=true:使用数据库生成的主键
    keyProperty="id":把生成的主键回填到实体的 id 属性
  → 插入后,user.getId() 就是数据库生成的自增主键(自动回填)

XML 写法:
  <insert id="insert" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user(name) VALUES(#{name})
  </insert>

原理:MyBatis 通过 JDBC 的 getGeneratedKeys() 拿回生成的主键
  再 set 到实体的 keyProperty 字段

不支持自增的数据库(如 Oracle 序列):
  用 <selectKey> 指定获取主键的 SQL(如 SELECT seq.nextval)

插入回填自增主键用 @Options(useGeneratedKeys=true, keyProperty="id")(或 XML 的 useGeneratedKeys="true" keyProperty="id")——插入后 MyBatis 把数据库生成的自增主键回填到实体的 id 字段user.getId() 就是主键),不用再查一次。原理是通过 JDBC 的 getGeneratedKeys() 拿回主键再 set 到实体。不支持自增的数据库(Oracle 序列)用 <selectKey> 指定获取主键的 SQL。理解「useGeneratedKeys+keyProperty 插入后回填自增主键到实体(不用再查)、原理是 JDBC getGeneratedKeys、Oracle 序列用 selectKey」,就掌握了返回主键。

五、Map 返回的取舍

理解「什么时候该用 Map、什么时候该用实体」:

返回 Map 的优点:
  ✓ 灵活——不用定义实体类
  ✓ 适应列不固定的动态查询

返回 Map 的缺点:
  ✗ 无类型安全——value 是 Object,要强转
  ✗ 字符串 key 易写错列名(写错返回 null,静默)
  ✗ IDE 不能补全/检查/重构
  ✗ 可读性差——不知道这个 Map 里有啥

取舍原则:
  能定义实体就定义实体(resultType="User"):
    - 类型安全、IDE 友好、可读、可维护
    - 列固定的业务查询都应该用实体

  用 Map 的合理场景(少数):
    - 临时/一次性查询,不值得建实体
    - 列不固定的动态查询(报表、动态字段)
    - 通用查询工具、数据导出
    - 只查一两个聚合值(COUNT、SUM)

所以:Map 是"灵活但不安全"的选择,节制使用
  业务代码优先实体,Map 用于特殊场景

Map 返回的取舍:优点是灵活(不用定义实体、适应列不固定),缺点是无类型安全(value 是 Object 要强转)、字符串 key 易写错、IDE 不友好、可读性差。原则「能定义实体就定义实体」(类型安全、IDE 友好、可维护,业务查询都应用实体);Map 只用于少数场景(临时查询、列不固定的动态查询/报表、通用工具、只查聚合值)。理解「Map 灵活但不安全(无类型安全/字符串 key 易错/IDE 不友好)、能定义实体就定义、Map 用于临时/列不固定/聚合值等特殊场景」,就掌握了 Map 返回的取舍。

六、实践小结

把这几个用法的适用场景整理清楚:

返回类型速查:
  单个实体      → resultType="User"(列固定,最常用)
  多个实体      → List<User>(列固定)
  单行 Map      → Map<String,Object>(不想建实体/列不固定)
  多行 List<Map> → List<Map<...>>(多行列不固定,如报表)
  索引 Map      → @MapKey("id") Map<Long,User>(要按字段快速查找)
  插入拿主键    → useGeneratedKeys + keyProperty(回填实体 id)

选择逻辑:
  ① 列固定的业务查询 → 用实体(类型安全,首选)
  ② 列不固定/临时 → 用 Map
  ③ 要按字段快速查找一批数据 → @MapKey 建索引 Map
  ④ 插入后要用生成的主键 → useGeneratedKeys 回填

记忆:实体优先,Map 用于灵活场景,@MapKey 用于查找,
     useGeneratedKeys 用于拿主键

实践小结:列固定的业务查询用实体(首选、类型安全);列不固定/临时用 Map;要按字段快速查找一批数据用 @MapKey 建索引 Map;插入后要用主键用 useGeneratedKeys 回填。核心记忆「实体优先,Map 用于灵活场景,@MapKey 用于查找,useGeneratedKeys 用于拿主键」。理解「返回类型选择:列固定用实体/列不固定用 Map/按字段查找用 @MapKey/插入拿主键用 useGeneratedKeys」,就掌握了这几个用法的整体选择。

记忆钩子:「MyBatis 返回 Map 三种:①单行 Map<String,Object>(key=列名 value=列值,适合不想建实体/列不固定)②多行 List(每行一个 Map,适合动态报表)③@MapKey(‘id’) Map<Long,User>(把 List 转成以字段为 key 的 Map=建索引,map.get(id)O(1)查找比 List 遍历快,字段要唯一);插入拿自增主键用 @Options(useGeneratedKeys=true,keyProperty=‘id’)回填实体 id(原理 JDBC getGeneratedKeys,Oracle 序列用);★Map 灵活但无类型安全(value 是 Object/字符串 key 易错/IDE 不友好)→能定义实体就定义,Map 用于临时/列不固定/聚合值」

七、常见误区与追问

  • 误区:查询只能返回实体或 List<实体>。 还能返回 Map<String,Object>(单行)、List(多行)、@MapKey 指定的 Map<K,实体>(索引 Map);适合不想定义实体、列不固定、或要按字段快速查找的场景。
  • 误区:返回 Map 比返回实体好(更灵活)。 Map 牺牲了类型安全和可读性——value 是 Object 要强转、字符串 key 易写错列名(写错静默返回 null)、IDE 不能补全/检查/重构;能定义实体就定义实体,Map 只用于临时、列不固定的特殊场景。
  • 误区:@MapKey 和普通返回 Map 一样。 不同——普通 Map<String,Object> 是「单行的列名→列值」;@MapKey 是把「多行 List 结果」转成「以某字段为 key 的 Map」(如 Map<Long,User>),相当于建索引,方便按字段 O(1) 快速查找。
  • 误区:插入后要再查一次才能拿到自增主键。 不用——用 useGeneratedKeys=true + keyProperty=“id”,MyBatis 插入后通过 JDBC 的 getGeneratedKeys() 把生成的主键自动回填到实体的 id 字段,直接 user.getId() 就能拿到,不用再查。
  • 追问:@MapKey 有什么实用价值? 把查出的一批数据转成「字段→对象」的 Map(相当于建索引),后续要反复按某字段(通常是主键/唯一键)查找时是 O(1),比在 List 里遍历找(O(n))高效;适合「查出一批数据后频繁按某字段查找」的场景;注意 key 字段要唯一,否则重复 key 会覆盖。
  • 追问:Oracle 这种用序列的数据库怎么拿插入主键? Oracle 不支持自增(用序列),不能用 useGeneratedKeys;要用 标签指定获取主键的 SQL(如 SELECT seq.nextval FROM dual),设 order=“BEFORE”(插入前取序列值)或 “AFTER”,把值设到 keyProperty 指定的字段。
  • 追问:什么时候该用 Map 返回、什么时候用实体? 列固定的业务查询用实体(resultType=“User”,类型安全、IDE 友好、可维护,首选);列不固定的动态查询(报表、动态字段)、临时一次性查询、只查一两个聚合值时用 Map(灵活但无类型安全);原则是「能定义实体就定义实体,Map 节制使用」。

八、加强记忆

MyBatis 除了返回实体,还能返回 Map,常见三种① 单行 Map<String,Object>(key=列名、value=列值,适合不想定义实体/列不固定);② 多行 List<Map<String,Object>>(每行一个 Map,适合动态报表/列不固定);@MapKey("id") Map<Long,User>(把「多行 List 结果」转成「以某字段为 key 的 Map」——相当于建索引map.get(id)O(1) 查找,比 List 遍历 O(n) 快,适合查出一批数据后频繁按字段查找,key 字段应唯一否则覆盖)。插入回填自增主键@Options(useGeneratedKeys=true, keyProperty="id")(或 XML 同名属性)——插入后 MyBatis 通过 JDBC 的 getGeneratedKeys() 把主键回填到实体 id 字段(不用再查),Oracle 序列用 <selectKey>Map 返回灵活但牺牲类型安全(value 是 Object 要强转、字符串 key 易写错、IDE 不友好)——能定义实体就定义实体(业务查询首选),Map 只用于临时/列不固定/聚合值等特殊场景。一句话「MyBatis 返回 Map:单行 Map<String,Object>(列名→列值)/多行 List(动态报表)/@MapKey(‘id’)Map<K,实体>(建索引 O(1)查找);插入拿主键用 useGeneratedKeys+keyProperty 回填实体 id(Oracle 用 selectKey);Map 无类型安全→能定义实体就定义、Map 用于临时列不固定场景」。