1. 项目概述:为什么我们需要关注MyBatis的关联映射?
如果你用过MyBatis,肯定遇到过这样的场景:查询一个订单,需要顺带把订单里的所有商品项也查出来;或者反过来,查询一个商品项,需要知道它属于哪个订单。这就是典型的一对多和多对一关系。在数据库里,我们用外键来维系这种关系,但在Java对象的世界里,我们希望得到的是一个结构清晰、嵌套完整的对象树。MyBatis的<collection>和<association>标签,就是连接数据库表关系与Java对象关系的桥梁。
很多开发者,尤其是刚接触MyBatis的朋友,对这两个标签的使用往往停留在“复制粘贴”阶段。配置文件写出来了,数据也能查出来,但一旦遇到N+1查询问题、复杂的嵌套结果映射,或者性能瓶颈,就有点束手无策。更别提去理解MyBatis底层是如何组装这些关联数据的。这次,我们不只讲怎么用,更要拆开揉碎了讲清楚背后的原理、不同写法的优劣,以及我在实际项目中踩过的那些坑。我会用一个贯穿始终的“博客系统”案例来演示,这个案例包含用户、文章、评论,关系清晰,非常贴近实际开发。
2. 核心概念与模型设计:建立清晰的领域认知
在深入代码之前,我们必须先把业务模型和数据库设计理清楚。模糊的领域认知是导致后续映射混乱的根源。
2.1 案例业务模型定义
我们模拟一个简单的博客系统,核心实体有三个:
- 用户(User):系统的作者。
- 文章(Article):用户发表的内容。
- 评论(Comment):用户对文章的回复。
它们之间的关系是:
- 一个用户可以写多篇文章。这是一对多关系(User -> List
<Article>)。 - 一篇文章属于一个用户。这是多对一关系(Article -> User)。
- 一篇文章可以有多条评论。这是一对多关系(Article -> List
<Comment>)。 - 一条评论属于一篇文章,同时也由一个用户发表。这是两个多对一关系(Comment -> Article, Comment -> User)。
2.2 数据库表结构设计
根据上述模型,我们设计三张表:
-- 用户表 CREATE TABLE `user` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', `username` VARCHAR(50) NOT NULL COMMENT '用户名', `email` VARCHAR(100) COMMENT '邮箱' ); -- 文章表 CREATE TABLE `article` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', `user_id` BIGINT NOT NULL COMMENT '作者ID,外键关联user.id', `title` VARCHAR(200) NOT NULL COMMENT '文章标题', `content` TEXT COMMENT '文章内容', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ); -- 评论表 CREATE TABLE `comment` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', `article_id` BIGINT NOT NULL COMMENT '文章ID,外键关联article.id', `user_id` BIGINT NOT NULL COMMENT '评论者ID,外键关联user.id', `content` TEXT NOT NULL COMMENT '评论内容', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '评论时间', FOREIGN KEY (`article_id`) REFERENCES `article`(`id`), FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) );2.3 Java实体类设计
与表结构对应,我们创建Java实体类。这里的关键是,实体类的属性要能体现对象间的关联关系。
User.java (用户实体)
public class User { private Long id; private String username; private String email; // 一对多:一个用户拥有多篇文章 private List<Article> articles; // 一对多:一个用户发表过多条评论(根据业务需求可选) private List<Comment> comments; // 构造方法、getter、setter省略... }Article.java (文章实体)
public class Article { private Long id; private String title; private String content; private Date createTime; // 多对一:一篇文章属于一个用户 private User author; // 一对多:一篇文章有多条评论 private List<Comment> commentList; // 构造方法、getter、setter省略... }Comment.java (评论实体)
public class Comment { private Long id; private String content; private Date createTime; // 多对一:一条评论属于一篇文章 private Article article; // 多对一:一条评论由一个用户发表 private User commentUser; // 构造方法、getter、setter省略... }注意:实体类中的集合(如
List<Article>)和对象属性(如User author)就是我们需要用MyBatis关联映射来填充的。数据库查回来的扁平化结果集,MyBatis会根据我们的配置,自动“折叠”成这种嵌套的对象树。
3. 多对一(<association>)映射详解与实战
多对一关系在查询中是更常见的一方。比如,查文章时带上作者信息,查评论时带上评论者和文章概要。<association>标签就是用来处理这种“一个对象内部包含另一个对象”的场景。
3.1<association>的两种配置方式
MyBatis提供了两种主流配置方式:嵌套结果映射(ResultMap)和嵌套查询(Nested Select)。两者各有优劣,适用场景不同。
3.1.1 方式一:嵌套结果映射(推荐用于关联数据一次性查询)
这是性能最好、也是最常用的方式。通过单条SQL联表查询,一次性获取所有数据,然后通过<resultMap>描述复杂的映射关系。
目标:查询文章详情,并包含作者(User)的完整信息。
ArticleMapper.xml 配置:
<!-- 1. 定义包含关联的完整结果映射 --> <resultMap id="ArticleWithAuthorResultMap" type="Article"> <!-- 映射文章自身字段 --> <id property="id" column="id"/> <result property="title" column="title"/> <result property="content" column="content"/> <result property="createTime" column="create_time"/> <!-- 2. 使用 association 映射“多对一”的作者对象 --> <!-- property: Article实体类中的属性名,即 author --> <!-- javaType: 该属性的Java类型 --> <!-- 内部使用 result 子标签映射 User 的字段 --> <association property="author" javaType="User"> <id property="id" column="user_id"/> <!-- 注意:column 对应查询结果中的列名 --> <result property="username" column="username"/> <result property="email" column="user_email"/> <!-- 别名避免列名冲突 --> </association> </resultMap> <!-- 3. 编写联表查询SQL,并使用上面定义的 resultMap --> <select id="selectArticleWithAuthorById" resultMap="ArticleWithAuthorResultMap"> SELECT a.id, a.title, a.content, a.create_time, a.user_id, -- 文章表的外键,也是用户表的主键 u.username, u.email AS user_email -- 使用别名,避免与后续可能出现的其他email列冲突 FROM article a LEFT JOIN user u ON a.user_id = u.id WHERE a.id = #{id} </select>关键点解析:
- 列名与别名:联表查询时,如果多张表有相同列名(如
id),MyBatis在映射时会混淆。必须为重复的列或需要特殊标识的列设置别名。上面我们将u.email别名为了user_email。 <id>标签的重要性:在<association>内部指定<id property="id" column="user_id">至关重要。MyBatis利用这个标识符来对关联对象进行去重。如果多篇文章是同一个作者,MyBatis通过这个id能识别出是同一个User对象,从而避免创建重复实例。- 联表选择:这里使用了
LEFT JOIN,即使文章没有对应的作者(理论上不应该),文章数据也会被查询出来,作者属性为null。根据业务需求,也可以使用INNER JOIN。
实操心得:对于这种确定性的、需要同时展示的关联数据(如文章和作者),强烈推荐使用嵌套结果映射。一条SQL搞定,数据库优化方便,没有额外的网络开销,是性能最优解。
3.1.2 方式二:嵌套查询(适用于关联数据可选或动态加载)
嵌套查询的方式是,先执行主查询,然后根据主查询结果中的某一列(通常是外键),再去执行另一个查询来获取关联对象。
ArticleMapper.xml 配置:
<!-- 1. 首先,需要一个根据ID查询User的映射语句 --> <select id="selectUserById" resultType="User"> SELECT id, username, email FROM user WHERE id = #{id} </select> <!-- 2. 定义Article的结果映射,association通过select属性引用另一个查询 --> <resultMap id="ArticleWithAuthorNestedSelectMap" type="Article"> <id property="id" column="id"/> <result property="title" column="title"/> <result property="content" column="content"/> <result property="createTime" column="create_time"/> <!-- property: Article中的author属性 column: 将当前查询结果中的`user_id`列的值,作为参数传递给`selectUserById` select: 指定要执行的嵌套查询语句的ID --> <association property="author" column="user_id" select="selectUserById"/> </resultMap> <!-- 3. 主查询,只查Article表 --> <select id="selectArticleWithAuthorByIdNested" resultMap="ArticleWithAuthorNestedSelectMap"> SELECT id, title, content, create_time, user_id FROM article WHERE id = #{id} </select>执行逻辑:MyBatis首先执行selectArticleWithAuthorByIdNested,得到一条Article记录。然后发现<association>配置,它会取出这条记录的user_id值,去执行selectUserById查询,并将结果赋值给Article对象的author属性。
警告:N+1查询问题嵌套查询最大的坑就是N+1查询问题。如果主查询返回N条文章,那么为了获取每篇文章的作者,会额外执行N次
selectUserById查询。总共执行1(主查询)+ N(关联查询)次SQL,性能在数据量大时是灾难性的。什么情况下可以用?
- 关联数据不是每次都需要(延迟加载)。
- 主查询结果集很小(比如最多几十条)。
- 关联查询本身非常复杂,不适合写在联表SQL中。
MyBatis提供了
fetchType属性来控制加载行为:<association property="author" column="user_id" select="selectUserById" fetchType="lazy"/>设置为
lazy后,只有当你真正访问article.getAuthor()时,MyBatis才会去执行嵌套查询。这需要全局配置中开启延迟加载。
我的建议:默认情况下,优先使用嵌套结果映射(联表查询)。除非你非常清楚嵌套查询带来的性能影响,并且有明确的延迟加载需求,否则不要轻易使用。
3.2 处理多个多对一关系
评论(Comment)对象关联了文章和用户两个对象,是演示多重<association>的绝佳例子。
目标:查询评论时,同时获取评论对应的文章简要信息(如标题)和评论者的信息。
CommentMapper.xml 配置(使用嵌套结果映射):
<resultMap id="CommentWithArticleAndUserResultMap" type="Comment"> <id property="id" column="cid"/> <!-- 评论ID --> <result property="content" column="content"/> <result property="createTime" column="comment_create_time"/> <!-- 关联文章对象 --> <association property="article" javaType="Article"> <id property="id" column="aid"/> <!-- 文章ID --> <result property="title" column="title"/> <!-- 这里不需要文章的全部内容,只取标题即可 --> </association> <!-- 关联用户对象(评论者) --> <association property="commentUser" javaType="User"> <id property="id" column="uid"/> <!-- 用户ID --> <result property="username" column="comment_username"/> </association> </resultMap> <select id="selectCommentDetailById" resultMap="CommentWithArticleAndUserResultMap"> SELECT c.id AS cid, c.content, c.create_time AS comment_create_time, a.id AS aid, a.title, u.id AS uid, u.username AS comment_username FROM comment c LEFT JOIN article a ON c.article_id = a.id LEFT JOIN user u ON c.user_id = u.id WHERE c.id = #{id} </select>注意事项:
- 别名管理:当三张表联查时,列名冲突的可能性更大。必须为每个
id和可能重复的列(如username)设置清晰的别名,并在<resultMap>中准确引用。良好的别名习惯(如表名_字段名或用途_字段名)能极大减少映射错误。 - 选择性映射:
<association>内部不需要映射关联对象的全部字段。像上面的Article,我们只关心id和title,这能减少不必要的数据传输和对象构造开销。
4. 一对多(<collection>)映射详解与实战
一对多关系是另一个核心场景,比如查看用户主页时列出其所有文章,或者查看文章时展示其所有评论。<collection>标签就是用来映射这种“一个对象包含一个对象集合”的关系。
4.1<collection>的两种配置方式
与<association>类似,<collection>也支持嵌套结果映射和嵌套查询。
4.1.1 方式一:嵌套结果映射(处理一对多结果集折叠)
这是处理一对多最经典也稍复杂的方式。SQL联表查询会返回多行数据(例如,一个用户对应多篇文章,查询会返回用户数据重复的多行),MyBatis需要根据主对象的ID将这些行“折叠”成一个对象及其集合。
目标:查询用户及其发表的所有文章。
UserMapper.xml 配置:
<!-- 1. 定义包含集合的结果映射 --> <resultMap id="UserWithArticlesResultMap" type="User"> <!-- 映射用户自身字段 --> <id property="id" column="uid"/> <!-- 核心:主对象的id --> <result property="username" column="username"/> <result property="email" column="email"/> <!-- 2. 使用 collection 映射“一对多”的文章集合 --> <!-- ofType: 指定集合中元素的Java类型 --> <collection property="articles" ofType="Article"> <id property="id" column="aid"/> <!-- 集合元素的id,用于去重 --> <result property="title" column="title"/> <result property="content" column="content"/> <result property="createTime" column="article_create_time"/> <!-- 注意:这里通常不需要再嵌套映射文章的author,否则会形成循环引用 --> </collection> </resultMap> <!-- 3. 编写联表查询SQL --> <select id="selectUserWithArticlesById" resultMap="UserWithArticlesResultMap"> SELECT u.id AS uid, u.username, u.email, a.id AS aid, a.title, a.content, a.create_time AS article_create_time FROM user u LEFT JOIN article a ON u.id = a.user_id WHERE u.id = #{userId} </select>MyBatis的“折叠”算法解析: 这是理解一对多嵌套结果映射的关键。当我们执行上面的SQL,数据库可能返回如下数据:
| uid | username | aid | title | article_create_time | |
|---|---|---|---|---|---|
| 1 | 张三 | zhangsan@example.com | 101 | MyBatis入门 | 2023-10-01 |
| 1 | 张三 | zhangsan@example.com | 102 | Spring实战 | 2023-10-05 |
MyBatis在遍历结果集时:
- 首先看到第一行,根据
uid=1创建一个User对象,并初始化一个空的List<Article>。 - 将第一行的文章数据(id=101)创建一个
Article对象,加入到这个User的articles列表中。 - 移动到第二行,发现
uid还是1,它知道这是同一个User对象(通过<id column="uid">判断)。 - 于是不再创建新的
User对象,而是将第二行的文章数据(id=102)创建为另一个Article对象,追加到同一个User对象的articles列表中。 - 最终,我们得到一个
User对象,其articles列表包含两篇文章。
实操心得:确保主对象(User)的<id>映射正确,是<collection>能正常工作的前提。这个id是MyBatis识别行是否属于同一个主对象的依据。
4.1.2 方式二:嵌套查询(及其严重性能问题)
与<association>类似,<collection>也可以使用嵌套查询。
UserMapper.xml 配置:
<!-- 先定义查询用户文章列表的语句 --> <select id="selectArticlesByUserId" resultType="Article"> SELECT id, title, content, create_time FROM article WHERE user_id = #{userId} </select> <resultMap id="UserWithArticlesNestedSelectMap" type="User"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="email" column="email"/> <!-- column="id" 将用户的id传给嵌套查询 --> <collection property="articles" column="id" ofType="Article" select="selectArticlesByUserId"/> </resultMap> <select id="selectUserWithArticlesByIdNested" resultMap="UserWithArticlesNestedSelectMap"> SELECT id, username, email FROM user WHERE id = #{userId} </select>性能警示:这种方式同样存在N+1查询问题,而且对于一对多,问题可能更严重。如果主查询返回N个用户,每个用户有M篇文章,那么总查询次数是 1 + N * M?不对,实际上是1(主查询) + N(每个用户触发一次selectArticlesByUserId)。虽然每个嵌套查询可能返回多行,但数据库交互次数仍然是N+1次,当用户量很大时,性能开销巨大。
结论:对于一对多查询,嵌套结果映射(联表查询)通常是唯一可行的选择,除非你的数据量极小,或者你明确使用了延迟加载并且关联数据访问频率很低。
4.2 复杂嵌套:多层级关联映射
现实中的对象关系往往更复杂,比如我们想查询一篇文章,包含其作者信息,以及文章下的所有评论,而每条评论又包含评论者的信息。这就形成了多层级嵌套。
目标:查询文章详情,包含作者,以及文章下的所有评论(每条评论包含评论者信息)。
ArticleMapper.xml 配置:
<resultMap id="ArticleDetailResultMap" type="Article"> <id property="id" column="a_id"/> <result property="title" column="title"/> <result property="content" column="content"/> <result property="createTime" column="a_create_time"/> <!-- 多对一:作者 --> <association property="author" javaType="User"> <id property="id" column="u_id"/> <result property="username" column="author_name"/> <result property="email" column="author_email"/> </association> <!-- 一对多:评论列表 --> <collection property="commentList" ofType="Comment"> <id property="id" column="c_id"/> <result property="content" column="comment_content"/> <result property="createTime" column="c_create_time"/> <!-- 评论内部又有一个多对一:评论者 --> <association property="commentUser" javaType="User"> <id property="id" column="commenter_id"/> <result property="username" column="commenter_name"/> </association> </collection> </resultMap> <select id="selectArticleDetailById" resultMap="ArticleDetailResultMap"> SELECT a.id AS a_id, a.title, a.content, a.create_time AS a_create_time, u.id AS u_id, u.username AS author_name, u.email AS author_email, c.id AS c_id, c.content AS comment_content, c.create_time AS c_create_time, cu.id AS commenter_id, cu.username AS commenter_name FROM article a LEFT JOIN user u ON a.user_id = u.id LEFT JOIN comment c ON a.id = c.article_id LEFT JOIN user cu ON c.user_id = cu.id WHERE a.id = #{articleId} ORDER BY c.create_time ASC -- 对评论排序 </select>这个查询的挑战与技巧:
- 笛卡尔积爆炸:这是多表
LEFT JOIN,特别是多层一对多关联时最危险的问题。一篇文章有1个作者,10条评论。联表后,结果集行数 = 1 (作者) * 10 (评论) = 10行。作者的信息在这10行中重复了10次。如果还有更多层嵌套,数据冗余会呈指数级增长。 - 列名别名至关重要:三张表(article, user(作者), user(评论者))都有
id和username,必须用有意义的别名(如u_id,author_name,commenter_id,commenter_name)严格区分。 - 排序:在SQL中使用
ORDER BY对集合元素(如评论)进行排序,可以保证在Java集合中元素的顺序符合预期。
我的经验:对于这种深度嵌套且数据量可能较大的查询,需要非常谨慎。要评估结果集的行数。如果一篇文章有上百条评论,查询返回的数据量就会很大。在实际项目中,我们往往会采用分步查询或业务层组装的方式来替代这种复杂的单次联表查询,以平衡复杂度和性能。
5. 高级技巧、性能优化与避坑指南
掌握了基本用法,我们来看看如何用得更好、更稳。这些经验很多是文档里不会写的,都是实践中踩坑踩出来的。
5.1 延迟加载(懒加载)的配置与权衡
延迟加载可以解决N+1查询问题中的“立即加载”带来的性能损耗。它的核心思想是:只有当你真正用到关联数据时,MyBatis才发出SQL去查询。
全局配置(mybatis-config.xml 或 application.yml):
<settings> <!-- 开启全局延迟加载开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 将积极加载改为按需加载(3.4.1版本后默认是false,按需加载) --> <setting name="aggressiveLazyLoading" value="false"/> </settings>在映射文件中使用:
<!-- 嵌套查询方式,并指定为懒加载 --> <association property="author" column="user_id" select="selectUserById" fetchType="lazy"/> <collection property="articles" column="id" select="selectArticlesByUserId" fetchType="lazy"/>注意事项与坑:
- “懒加载”与“序列化”的冲突:这是最常见的坑。当你开启懒加载后,对象里包含的是MyBatis动态生成的代理对象。如果你将这个对象直接转换成JSON(例如通过Spring MVC的
@ResponseBody)返回给前端,JSON序列化工具(如Jackson)在遍历对象属性时,会触发代理对象的getter方法,从而导致懒加载查询被执行!这可能导致你期望的懒加载失效,甚至引发意外的数据库查询和性能问题。- 解决方案:使用DTO(Data Transfer Object)来替代直接返回实体。在Service层,将懒加载的实体对象转换为只包含所需字段的普通POJO DTO,再返回给控制器。
- Session生命周期:懒加载查询需要数据库连接(SqlSession)仍然存活。在Web应用中,通常通过“Open Session In View”模式来保持Session直到视图渲染完成。但在非Web环境或自定义Session管理时,如果Session提前关闭,访问懒加载属性会抛出异常。
- 性能权衡:懒加载把一次大的联表查询,拆成了多次小的查询。在关联数据访问概率低的情况下是优势;但如果访问概率高,反而会增加总的数据库交互次数和延迟。没有银弹,需要根据具体场景分析。
5.2 使用<sql>片段和<include>复用映射代码
当多个查询结果映射有相同的字段集合时,可以使用<sql>定义片段,用<include>引入,保持代码整洁。
<!-- 定义用户字段的SQL片段 --> <sql id="userColumns"> id, username, email </sql> <!-- 定义用户结果映射的片段 --> <sql id="userResultMap"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="email" column="email"/> </sql> <!-- 在查询中使用 --> <select id="selectUserById" resultType="User"> SELECT <include refid="userColumns"/> FROM user WHERE id = #{id} </select> <!-- 在结果映射中使用 --> <resultMap id="SomeResultMap" type="Xxx"> ... <association property="user" javaType="User"> <include refid="userResultMap"/> </association> ... </resultMap>5.3 鉴别器(<discriminator>)的妙用
这是一个高级但非常有用的功能,用于根据某列的值决定如何映射不同的关联对象。典型的例子是:一个消息表,有系统消息、评论消息、点赞消息等类型,它们关联的目标实体不同。
假设我们有一个notification表,其中type字段区分消息类型,target_id关联不同表的主键。
<resultMap id="NotificationResultMap" type="Notification"> <id property="id" column="id"/> <result property="content" column="content"/> <result property="type" column="type"/> <!-- 根据type字段的值,决定如何映射target对象 --> <discriminator javaType="int" column="type"> <case value="1" resultMap="commentNotificationMap"/> <!-- 评论消息 --> <case value="2" resultMap="likeNotificationMap"/> <!-- 点赞消息 --> </discriminator> </resultMap> <resultMap id="commentNotificationMap" type="CommentNotification" extends="NotificationResultMap"> <association property="target" javaType="Comment" select="selectCommentById" column="target_id"/> </resultMap> <resultMap id="likeNotificationMap" type="LikeNotification" extends="NotificationResultMap"> <association property="target" javaType="Like" select="selectLikeById" column="target_id"/> </resultMap>5.4 常见问题排查与调试技巧
映射失败,属性为null
- 检查点1:列名与别名:这是90%的问题所在。仔细核对SQL查询返回的列名与
<result>/<association>/<collection>中的column属性是否完全一致,包括大小写(取决于数据库配置)。 - 检查点2:属性名:核对
property属性与Java实体类的字段名是否一致。 - 检查点3:日志:开启MyBatis的SQL日志(设置日志级别为
DEBUG),查看实际执行的SQL和返回的结果集,与你的映射配置逐列对比。
- 检查点1:列名与别名:这是90%的问题所在。仔细核对SQL查询返回的列名与
一对多映射结果集合中对象重复
- 原因:
<collection>中忘记指定集合元素对象的<id>。MyBatis需要<id>来对集合内的元素进行去重。 - 解决:确保在
<collection>内部为集合元素类型(如<Article>)正确配置了<id>映射。
- 原因:
嵌套查询导致N+1问题,性能慢
- 识别:观察日志,是否执行了1条主查询和大量类似的子查询。
- 解决:评估是否能用嵌套结果映射(联表)替代。如果不能,考虑是否真的需要所有数据,或者使用延迟加载并确保不会在序列化时意外触发。
复杂联表查询结果混乱
- 原因:多层一对多关联导致笛卡尔积,数据冗余严重,可能影响MyBatis的“折叠”逻辑。
- 解决:考虑拆分成多个简单的查询,在Service层进行数据组装。或者,使用MyBatis的
@ResultMap注解配合多个<resultMap>进行更精细的控制。
循环引用(StackOverflowError)
- 场景:
User里有List<Article>,Article里又有User author。当双向引用且都使用嵌套结果映射时,JSON序列化或toString()方法可能导致无限递归。 - 解决:
- 打破循环:在某一方(通常是多方)的映射中,不配置对另一方的关联。例如,在
Article的author映射中,只映射id和name,而不让author对象再拥有articles属性(在查询时)。 - 使用DTO:这是最根本的解决方案,DTO根据视图需求设计,天然避免了实体间的复杂循环引用。
- 序列化注解:使用Jackson的
@JsonIgnoreProperties注解在实体类上忽略对方的属性。
- 打破循环:在某一方(通常是多方)的映射中,不配置对另一方的关联。例如,在
- 场景:
关联映射是MyBatis的灵魂功能之一,它强大但需要精心设计。理解其原理(结果集折叠、嵌套查询触发)比记住语法更重要。在简单的CRUD中使用它事半功倍,在复杂的业务关联中则需要权衡联表查询的复杂度与多次查询的代价。记住,没有最好的方案,只有最适合当前场景的方案。从简单的嵌套结果映射开始,在遇到性能瓶颈或特殊需求时,再考虑延迟加载、分步查询等高级策略,并始终将代码清晰性和可维护性放在重要位置。