MyBatis中Collection和Association的底层实现原理分析

 更新时间:2025年11月28日 08:44:17   作者:shy好好学习  
本文详细介绍了MyBatis中`<collection>`和`<association>`标签的底层实现原理,探讨了它们如何通过缓存和对象创建机制将数据库结果集高效地映射为Java对象,文章通过实际的数据库示例和伪代码示例,展示了这两个标签在一对多和一对一关系中的具体工作方式

引言

在 MyBatis 中,<collection><association> 标签用于处理一对多和一对一的关系。

这两个标签在底层通过缓存、对象创建和反射机制,将数据库结果集高效地映射为 Java 对象。

本文将重点探讨这两个标签的底层实现原理,并结合实际的数据库示例说明其工作机制。

1. Collection 的底层映射原理

1.1 一对多映射场景

<collection> 标签主要用于表示一对多关系的映射。

典型的一对多场景是在一个表中有重复的主对象记录(如用户),但每个主对象包含多个子对象(如订单)。

示例数据库结构

假设我们有以下结果集,表示用户和订单的关系:

user_idusernameorder_idorder_date
1Alice1012023-01-01
1Alice1022023-02-01
2Bob1032023-03-01

这个数据表示,用户 Alice 有两个订单,用户 Bob 有一个订单。我们希望将其映射为一个 User 对象,每个用户对象包含一个订单集合。

MyBatis 映射配置

<resultMap id="userResultMap" type="com.example.User">
    <id property="userId" column="user_id"/>
    <result property="username" column="username"/>
    <collection property="orders" ofType="com.example.Order">
        <id property="orderId" column="order_id"/>
        <result property="orderDate" column="order_date"/>
    </collection>
</resultMap>

在这个映射中,<collection> 标签表示用户对象 User 可能包含多个 Order 对象,ofType 定义了集合中元素的类型。

1.2 底层映射机制

结果集处理

当执行 SQL 查询时,MyBatis 会获取完整的结果集。由于用户可能有多个订单,查询结果会包含重复的 user_idusername

主对象缓存处理

MyBatis 使用主键 user_id 来判断是否已经创建了对应的 User 对象(如果没有主键, 会使用其他键来模拟唯一键)。

  • 如果 user_id 相同,表示当前行属于同一个用户,则重用该用户对象。
  • 如果 user_id 不同,MyBatis 会创建新的 User 对象。

集合属性映射

对于每一行的订单信息(order_idorder_date),MyBatis 会为当前的用户创建新的 Order 对象,并将其添加到用户的 orders 集合中。

对象重用与集合管理

通过缓存机制,MyBatis 确保每个用户只创建一次,而订单则根据不同的 order_id 创建。对于相同的用户,每个订单行都会被添加到 orders 集合中。

底层逻辑代码示例(伪代码)

// 查询用户缓存处理
if (!cache.containsKey(userId)) {
    User user = new User();
    user.setUserId(resultSet.getInt("user_id"));
    user.setUsername(resultSet.getString("username"));
    cache.put(userId, user);
}

// 处理订单集合
Order order = new Order();
order.setOrderId(resultSet.getInt("order_id"));
order.setOrderDate(resultSet.getDate("order_date"));

// 将订单添加到用户的集合
user.getOrders().add(order);

MyBatis 会缓存用户对象,并通过 getOrders().add(order) 方法将每个新创建的订单对象加入到用户的订单集合中。

2. Association 的底层映射原理

2.1 一对一映射场景

<association> 标签用于处理一对一或多对一的映射。在这种场景下,通常我们希望将两个表的结果组合为一个主对象和一个关联对象。例如,用户和用户详情是一对一的关系。

示例数据库结构

假设我们有以下结果集,表示用户及其详细信息:

user_idusernamedetail_idageaddress
1Alice125Wonderland
2Bob230Wonderland

这个数据表示,每个用户有一条与之关联的详细信息记录。我们希望将其映射为 User 对象,其中包含一个 UserDetail 对象。

MyBatis 映射配置

<resultMap id="userWithDetailResultMap" type="com.example.User">
    <id property="userId" column="user_id"/>
    <result property="username" column="username"/>
    <association property="detail" javaType="com.example.UserDetail">
        <id property="detailId" column="detail_id"/>
        <result property="age" column="age"/>
        <result property="address" column="address"/>
    </association>
</resultMap>

在这个映射中,<association> 标签用于将用户与详细信息关联起来,javaType 指定了 UserDetail 对象的类型。

2.2 底层映射机制

主对象创建与缓存

当执行查询时,MyBatis 会首先根据 user_id 创建或查找缓存中的用户对象。类似于 <collection>,如果用户对象已经存在,则不会重新创建。

关联对象的映射

对于每一行,MyBatis 根据 detail_id 创建 UserDetail 对象,并通过 MetaObject 机制,将查询结果的 ageaddress 列映射到 UserDetail 对象中。

关联对象赋值

一旦 UserDetail 对象创建完成,MyBatis 会将其赋值给 User 对象的 detail 属性。这是通过反射完成的。

底层逻辑代码示例(伪代码)

// 用户缓存处理
User user = cache.get(userId);

// 创建并映射用户详细信息
UserDetail detail = new UserDetail();
detail.setDetailId(resultSet.getInt("detail_id"));
detail.setAge(resultSet.getInt("age"));
detail.setAddress(resultSet.getString("address"));

// 将详细信息赋值给用户对象
user.setDetail(detail);

MyBatis 在映射时会通过 MetaObject 访问 User 对象的 detail 属性,并使用反射为其赋值。这个过程使得每个用户对象都能够正确关联其详细信息。

3. 总结

MyBatis 中的 <collection><association> 标签通过缓存、反射与对象创建机制,完成数据库结果集到 Java 对象的映射。在处理一对多和一对一关系时,这两个标签的底层机制可以高效地组织复杂的对象结构。

  • <collection> 标签:通过主对象的缓存和子对象集合的动态添加,实现了复杂的一对多关系的映射。
  • <association> 标签:通过关联对象的创建和属性映射,实现了一对一或多对一的关联映射。

理解这些底层机制能够帮助开发者优化查询性能,并更好地设计数据结构,充分发挥 MyBatis 的 ORM 功能。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 关于scanner.nextInt()等next()和scanner.nextIine()连用注意事项

    关于scanner.nextInt()等next()和scanner.nextIine()连用注意事项

    这篇文章主要介绍了关于scanner.nextInt()等next()和scanner.nextIine()连用注意事项,具有很好的参考价值,希望对大家有所帮助。
    2023-04-04
  • 解决SpringBoot运行报错:找不到或无法加载主类的问题

    解决SpringBoot运行报错:找不到或无法加载主类的问题

    这篇文章主要介绍了解决SpringBoot运行报错:找不到或无法加载主类的问题,具有很好的参考价值,对大家的学习或工作有一定的参考价值,需要的朋友可以参考下
    2023-09-09
  • SpringBoot生成二维码的实现

    SpringBoot生成二维码的实现

    这篇文章主要介绍了SpringBoot生成二维码的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • java8中:: 用法示例(JDK8双冒号用法)

    java8中:: 用法示例(JDK8双冒号用法)

    这篇文章主要给大家介绍了关于java8 中的:: 用法(JDK8双冒号用法)的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用java8具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-09-09
  • java开发接口吞吐量提升10多倍技巧

    java开发接口吞吐量提升10多倍技巧

    这篇文章主要为大家介绍了java开发技巧之接口吞吐量提升10多倍的方法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • Spring底层机制环境搭建全过程

    Spring底层机制环境搭建全过程

    本文介绍了如何创建和使用Spring框架,包括模块创建、依赖引入、环境搭建、Bean的生命周期管理、AOP编程以及代码托管,通过实际操作和代码示例,详细讲解了Spring的核心概念和功能
    2024-12-12
  • java关于String.split("|")的使用方式

    java关于String.split("|")的使用方式

    这篇文章主要介绍了java关于String.split("|")的使用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • springcloud gateway聚合swagger2的方法示例

    springcloud gateway聚合swagger2的方法示例

    这篇文章主要介绍了springcloud gateway聚合swagger2的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • 一文详解SpringBoot如何创建自定义的自动配置

    一文详解SpringBoot如何创建自定义的自动配置

    在实际开发中,仅靠SpringBoot的自动配置是远远不够的,所以这篇文章主要来和大家简单讲讲如何在SpringBoot创建自定义的自动配置吧
    2025-07-07
  • 解析Java8 Stream原理

    解析Java8 Stream原理

    说起 Java 8,我们知道 Java 8 大改动之一就是增加函数式编程,而 Stream API 便是函数编程的主角,Stream API 是一种流式的处理数据风格,也就是将要处理的数据当作流,在管道中进行传输,并在管道中的每个节点对数据进行处理,如过滤、排序、转换等
    2021-06-06

最新评论