MyBatis 中的一对多、多对一、多对多映射深度解析(含 Spring Boot 示例)

💡 一、前言

在实际开发中,我们经常需要处理多个表之间的关联关系。例如:

  • 一个用户可以发表多篇文章(一对多
  • 每篇文章属于一个用户(多对一
  • 一篇文章可以有多个标签,一个标签也可以属于多篇文章(多对多

而 MyBatis 作为 Java 领域最流行的 ORM 框架之一,提供了强大的对象关系映射能力,能够帮助我们优雅地处理这些复杂的关系。

本文将带你从零开始,手把手实现 MyBatis 中常见的三种关系映射,并结合 Spring Boot 给出完整示例,助你轻松应对面试与实战!


🛠️ 二、环境准备

1. 添加依赖(pom.xml

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- MyBatis Starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>

    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Lombok 简化实体类代码 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

2. 数据库配置(application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/myblog
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.demo.model

📚 三、业务场景与数据库设计

我们以三个常见业务模型为例:

表名描述
user(id, name)用户表
article(id,title,content,user_id)文章表
tag(id, name)标签表
article_tag(article_id, tag_id)中间表

对应关系:

  • UserArticle:一对多 / 多对一

  • ArticleTag:多对多


🧪 四、一对多(One-to-Many)映射实战

1. 定义实体类

// User.java
@Data
public class User {
    private Integer id;
    private String name;
    private List<Article> articles; // 一对多
}
// Article.java
@Data
public class Article {
    private Integer id;
    private String title;
    private String content;
    private Integer userId;
}

2. Mapper 接口

@Mapper
public interface UserMapper {
    List<User> findAllWithArticles();
}

3. XML 映射文件(UserMapper.xml)

✅ 方式一:联表查询一次性获取所有数据(推荐用于小数据量)
<mapper namespace="com.example.demo.mapper.UserMapper">

    <resultMap id="UserWithArticles" type="User">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <collection property="articles" ofType="Article">
            <id column="article_id" property="id"/>
            <result column="title" property="title"/>
            <result column="content" property="content"/>
        </collection>
    </resultMap>

    <select id="findAllWithArticles" resultMap="UserWithArticles">
        SELECT u.id, u.name,
               a.id AS article_id, a.title, a.content
        FROM user u
        LEFT JOIN article a ON u.id = a.user_id
    </select>

</mapper>
✅ 方式二:嵌套查询 + 延迟加载(适合大数据量)
<resultMap id="UserWithArticlesLazy" type="User">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <collection property="articles"
                column="id"
                fetchType="lazy"
                select="findArticlesByUserId"/>
</resultMap>

<select id="findAllWithArticlesLazy" resultMap="UserWithArticlesLazy">
    SELECT * FROM user
</select>

<select id="findArticlesByUserId" resultType="Article">
    SELECT * FROM article WHERE user_id = #{id}
</select>

🔁 五、多对一(Many-to-One)映射实战

1. 修改 Article 实体类

// Article.java
@Data
public class Article {
    private Integer id;
    private String title;
    private String content;
    private Integer userId;
    private User user; // 多对一
}

2. Mapper 接口

@Mapper
public interface ArticleMapper {
    List<Article> findAllWithUser();
}

3. XML 映射文件(ArticleMapper.xml)

<mapper namespace="com.example.demo.mapper.ArticleMapper">

    <resultMap id="ArticleWithUser" type="Article">
        <id column="id" property="id"/>
        <result column="title" property="title"/>
        <result column="content" property="content"/>
        <result column="user_id" property="userId"/>
        <association property="user" javaType="User">
            <id column="user_id" property="id"/>
            <result column="user_name" property="name"/>
        </association>
    </resultMap>

    <select id="findAllWithUser" resultMap="ArticleWithUser">
        SELECT a.id, a.title, a.content, a.user_id,
               u.name AS user_name
        FROM article a
        LEFT JOIN user u ON a.user_id = u.id
    </select>

</mapper>

🔁 六、多对多(Many-to-Many)映射实战

1. 修改 Article 实体类

// Article.java
private List<Tag> tags; // 多对多

2. Tag 实体类

// Tag.java
@Data
public class Tag {
    private Integer id;
    private String name;
}

3. Mapper 接口

@Mapper
public interface ArticleMapper {
    List<Article> findAllWithTags();
}

4. XML 映射文件(ArticleMapper.xml)

<resultMap id="ArticleWithTags" type="Article">
    <id column="id" property="id"/>
    <result column="title" property="title"/>
    <result column="content" property="content"/>
    <collection property="tags" ofType="Tag">
        <id column="tag_id" property="id"/>
        <result column="tag_name" property="name"/>
    </collection>
</resultMap>

<select id="findAllWithTags" resultMap="ArticleWithTags">
    SELECT a.id, a.title, a.content,
           t.id AS tag_id, t.name AS tag_name
    FROM article a
    LEFT JOIN article_tag at ON a.id = at.article_id
    LEFT JOIN tag t ON t.id = at.tag_id
</select>

🧭 七、进阶技巧与最佳实践

延迟加载 vs 立即加载

类型特点使用场景
立即加载一次性加载全部数据数据量小,实时性要求高
延迟加载用到时才查数据量大,节省内存资源

配置延迟加载:

mybatis:
  configuration:
    lazy-loading-enabled: true
    aggressive-lazy-loading: false

✅ 使用注解方式实现关联关系

@Select("SELECT * FROM article WHERE user_id = #{userId}")
@Results({
    @Result(property = "id", column = "id"),
    @Result(property = "user", column = "user_id",
            one = @One(select = "com.example.demo.mapper.UserMapper.findById"))
})
List<Article> findByUserId(Integer userId);

✅ 分页 + 关联数据查询优化

使用 PageHelper 分页时,如果直接查询带有关联数据的大结果集,可能会导致性能问题。建议:

  • 先分页主表数据;
  • 再根据主表 ID 批量查询关联数据。
PageHelper.startPage(pageNum, pageSize);
List<Article> articles = articleMapper.findAll(); // 查询主表
List<Integer> ids = articles.stream().map(Article::getId).toList();
List<Tag> tags = tagMapper.findByArticleIds(ids); // 批量查询关联数据

🔍 八、常见问题与解决方案

问题描述解决方案
返回集合为空SQL字段未正确映射检查 <collection> 和字段别名
延迟加载无效未开启全局配置设置 lazy-loading-enabled: true
多条记录重复联表查询导致笛卡尔积使用 <resultMap> 控制映射逻辑
注解方式无法识别方法返回类型不匹配确保使用 @Results 正确绑定属性

📘 九、总结

关系类型使用标签适用场景
一对一<association> 单个对象引用
一对多<collection> 映射集合属性
多对多<collection>通过中间表连接
嵌套查询<collection select="...">支持延迟加载

掌握这些核心映射技巧后,你可以:

  • 在后台管理系统中构建复杂的业务模型;
  • 在电商系统中实现商品与分类、标签等关联;
  • 在内容平台中高效管理文章、用户与评论的关系;
  • 应对 MyBatis 相关高频面试题。

📝 十、结语

无论是做后台开发还是参与企业级项目,MyBatis 的关联映射能力 都是必须掌握的核心技能。希望这篇文章能够帮助你更好地理解和应用 MyBatis 的一对多、多对一和多对多映射。

如果你觉得有用,欢迎点赞、收藏、转发给更多需要的朋友!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值