# 关联（association）

```xml
<association property="author" column="blog_author_id" javaType="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
</association>
```

**关联（association）元素处理“有一个”类型的关系**。 关联结果映射和其它类型的映射工作方式差不多。 你需要指定**目标属性名**以及属性的`javaType`（很多时候 MyBatis 可以自己推断出来），在必要的情况下你还可以设置 JDBC 类型，如果你想覆盖获取结果值的过程，还可以设置类型处理器。

<table><thead><tr><th width="166">属性</th><th>描述</th></tr></thead><tbody><tr><td><code>property</code></td><td>映射到 JavaBean 的属性名。如果用来匹配的 JavaBean 存在给定名字的属性，那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。无论是哪一种情形，你都<strong>可以使用通常的点式分隔形式进行复杂属性导航</strong>。 比如，你可以这样映射一些简单的东西：“username”，或者映射到一些复杂的东西上：“address.street.number”。</td></tr><tr><td><code>javaType</code></td><td>一个 Java 类的完全限定名，或一个类型别名。 如果你映射到一个 JavaBean，MyBatis 通常可以推断类型。然而，<mark style="color:orange;"><strong>如果你映射到的是 HashMap，那么你应该明确地指定 javaType 来保证行为与期望的相一致</strong>。</mark></td></tr><tr><td><code>jdbcType</code></td><td>JDBC 类型。<mark style="color:orange;"><strong>只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。</strong></mark>这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程，你需要对可能存在空值的列指定这个类型。</td></tr><tr><td><code>typeHandler</code></td><td>我们在前面讨论过默认的类型处理器。使用这个属性，你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名，或者是类型别名。</td></tr></tbody></table>

关联的不同之处是，你**需要告诉 MyBatis 如何加载关联**。MyBatis 有两种不同的方式加载关联：

* **嵌套 Select 查询**：通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
* **嵌套结果映射**：使用嵌套的结果映射来处理连接结果的重复子集。

## **关联的嵌套 Select 查询**

<table><thead><tr><th width="151">属性</th><th>描述</th></tr></thead><tbody><tr><td><code>column</code></td><td><strong>数据库中的列名，或者是列的别名</strong>。一般情况下，这和传递给 <code>resultSet.getString(columnName)</code>方法的参数一样。<br>注意：在使用<strong>复合主键</strong>的时候，你可以使用 <code>column="{prop1=col1,prop2=col2}"</code> 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 <code>prop1</code> 和 <code>prop2</code> 作为参数对象，被设置为对应嵌套 Select 语句的参数。</td></tr><tr><td><code>select</code></td><td>用于加载复杂类型属性的<strong>映射语句的 ID</strong>，它会从 column 属性指定的列中检索数据，作为参数传递给目标 select 语句。 具体请参考下面的例子。</td></tr><tr><td><code>fetchType</code></td><td><p>可选的。有效值为 <code>lazy</code> 和 <code>eager</code>。 指定属性后，将在映射中忽略<strong>全局配置</strong>参数 <code>lazyLoadingEnabled</code>，使用属性的值。</p><p><strong>eager表示立即执行sql语句，而lazy则表示只有使用到该字段时才会执行sql语句</strong></p></td></tr></tbody></table>

<mark style="color:blue;">**在下面的示例中，如果将 fetchType 设为 lazy，则当调用对象的 getAuthor 方法时才会执行 selectAuthor**</mark>**&#x20;（注意：在 MyBatis 的全局配置中，有一个参数为 aggressiveLazyLoading。这个参数的含义是，**<mark style="color:orange;">**当该参数设置为 true 时，对任意延迟属性的调用会使带有延迟加载属性的对象完整加载**</mark>**，反之，每种属性都将按需加载。因此，**<mark style="color:orange;">**要想 lazy 正常使用需要将 aggressiveLazyLoading 设置为 false**</mark>**）。**

示例：

{% code overflow="wrap" %}

```xml
<resultMap id="blogResult" type="Blog">
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
```

{% endcode %}

有两个 select 查询语句：一个用来加载博客（Blog），另外一个用来加载作者（Author），而且博客的结果映射描述了应该使用 `selectAuthor` 语句加载它的 author 属性。

这种方式虽然很简单，但**在大型数据集或大型数据表上表现不佳**。这个问题被称&#x4E3A;**“N+1 查询问题”**。 概括地讲，N+1 查询问题是这样子的：

* 你执行了一个单独的 SQL 语句来获取结果的一个列表（就是“+1”）。
* 对列表返回的每条记录，你执行一个 select 查询语句来为每条记录加载详细信息（就是“N”）。

这个问题会导致成百上千的 SQL 语句被执行。

## **关联的嵌套结果映射**

| 属性              | 描述                                                                                                                                                                                                                                                                                                  |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resultMap`     | 结果映射的 ID，可以将此关联的嵌套结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以<mark style="color:blue;">**将多表连接操作的结果映射成一个单一的**</mark><mark style="color:blue;">**&#x20;**</mark><mark style="color:blue;">**`ResultSet`**</mark>。这样的 `ResultSet` 有部分数据是重复的。 为了将结果集正确地映射到嵌套的对象树中, MyBatis 允许你“串联”结果映射，以便解决嵌套结果集的问题。 |
| `columnPrefix`  | 当连接多个表时，你可能会不得不使用列别名来避免在 `ResultSet` 中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。                                                                                                                                                                                                        |
| `notNullColumn` | 默认情况下，在至少一个被映射到属性的列不为空时，子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为，指定后，Mybatis 将只在这些列中任意一列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值：未设置（unset）。                                                                                                                                                                          |
| `autoMapping`   | 如果设置这个属性，MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。注意，本属性对外部的结果映射无效，所以不能搭配 `select` 或 `resultMap` 元素使用。默认值：未设置（unset）。                                                                                                                                                           |

```sql
<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    B.author_id     as blog_author_id,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio
  from Blog B 
  left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>
```

```xml
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>
```

在上面的例子中，你可以看到，博客（Blog）作者（author）的关联元素委托名为 **“authorResult”** 的结果映射来加载作者对象的实例。

非常重要： id 元素在嵌套结果映射中扮演着非常重要的角色。你应该总是指定一个或多个可以唯一标识结果的属性。 虽然，即使不指定这个属性，MyBatis 仍然可以工作，但是会产生严重的性能问题。 只需要指定可以唯一标识结果的最少属性。显然，你可以选择主键（复合主键也可以）。

现在，上面的示例使用了外部的结果映射元素来映射关联。这使得 Author 的结果映射可以被重用。 然而，**如果你不打算重用它，或者你更喜欢将你所有的结果映射放在一个具有描述性的结果映射元素中。 你可以直接将结果映射作为子元素嵌套在内**。

```xml
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
  </association>
</resultMap>
```

那如果博客（blog）有一个共同作者（co-author）该怎么办？select 语句看起来会是这样的：

```sql
<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio,
    CA.id           as co_author_id,
    CA.username     as co_author_username,
    CA.password     as co_author_password,
    CA.email        as co_author_email,
    CA.bio          as co_author_bio
  from Blog B
  left outer join Author A on B.author_id = A.id
  left outer join Author CA on B.co_author_id = CA.id
  where B.id = #{id}
</select>
```

由于结果中的列名与结果映射中的列名不同。你需要指定 `columnPrefix` 以便重复使用该结果映射来映射 co-author 的结果。

```xml
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author"   resultMap="authorResult" />
  <association property="coAuthor" resultMap="authorResult" columnPrefix="co_" />
</resultMap>
```
