1. 程式人生 > >mybatis-高階結果對映之一對多(你知道一對多的結果是如何合併的嗎)

mybatis-高階結果對映之一對多(你知道一對多的結果是如何合併的嗎)

在一對多的關係中, 主表的資料回對應關聯表中的多條資料。 因此, 查詢時就會查詢出多條結果, 所以, 向類似的情況我們會使用 List 來進行儲存關聯表中獲取到的資訊。

1 資料準備

建立以下的名為 mybatis 的資料庫, 並在其下建立4個表。

資料庫結構

在此就不貼出來建表的 SQL 語句了 , 感興趣的可以去我的 Github:mybatis-mapping 中獲取。

1.2 實體類, 介面和XML

使用 mybatis-程式碼生成器 生成相應的實體類, 介面和XML。

程式碼結構

以上為生成的專案結構。

2 一對多對映

2.1 collection集合對映

2.1.1 建立結果實體類

我們需要建立一個 BlogPostBO,

public class BlogPostBO extends Blog {

    private List<Post> posts;

    public List<Post> getPosts() {
        return posts;
    }

    public void setPosts(List<Post> posts) {
        this.posts = posts;
    }
}

該類繼承於 Blog 類, 並多了一個連結串列成員變數 posts, 我們後續獲取到的釋出的文章都在此連結串列中。

2.1.2 建立結果集

剛開始時, 是這樣子建立的。

<resultMap id="BlogPostBO"  type="com.homejim.mybatis.entity.BlogPostBO" extends="BaseResultMap">
    <collection property="posts" columnPrefix="post_" ofType="com.homejim.mybatis.entity.Post">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="blog_id" jdbcType="INTEGER" property="blogId" />
        <result column="draft" jdbcType="INTEGER" property="draft" />
        <result column="content" jdbcType="LONGVARCHAR" property="content" />
    </collection>
</resultMap>

此處注意一個問題, 在 collection 中, 我們用的是 ofType 來表示 List 中的 Pojo 的屬性。而不是 type

因為我們內部 Post 有對應的結果集, 可以引用另一個 Mapper 中的結果集, 就可以簡化變成下面這樣子:

<resultMap id="BlogPostBO"  type="com.homejim.mybatis.entity.BlogPostBO" extends="BaseResultMap">
    <collection property="posts" columnPrefix="post_" resultMap="com.homejim.mybatis.mapper.PostMapper.ResultMapWithBLOBs" />
</resultMap>

可以簡單了好多。

2.1.3 建立對應的方法和XML

首先就是在對應的 Mapper 介面下建立方法:

    /**
     * 獲取部落格及其釋出的文章內容 一對多
     * @return
     */
    List<BlogPostBO> selectBlogAndPostList();

同時在 XML 中建立對應的 SQL 語句:

<select id="selectBlogAndPostList"  resultMap="BlogPostBO">
    SELECT
    b.id,
    b.title,
    b.author_id,
    p.id as post_id,
    p.blog_id as post_blog_id,
    p.draft as post_draft,
    p.content as post_content
  FROM blog b left join post p on p.blog_id=b.id
  </select>

2.1.4 測試

    /**
     *  resultMap + collection 一對多對映
     */
    @Test
    public void testSelectBlogAndPostListByBlog() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
        List<BlogPostBO> blogPostBOs = blogMapper.selectBlogAndPostList();

        sqlSession.close();

        for (int i = 0; i < blogPostBOs.size(); i++) {
            BlogPostBO blogPostBO = blogPostBOs.get(i);
            System.out.println(blogPostBO.getTitle());

            for (int j = 0; j < blogPostBO.getPosts().size(); j++) {
                System.out.println(blogPostBO.getPosts().get(j).getContent());
            }

            System.out.println("=============這是物件分割線===============");
        }
    }

測試結果

執行結果

可以看出, 已經獲取到了。

2.1.5 結果合併原理

剛開始的時候, 我們獲取到的結果在資料庫中應該是這樣子的

資料庫結果

那麼, 結果是怎麼變成下面這樣子的呢?

程式碼結果

2.1.5.1 合併的依據

mybatis 在處理結果時, 會判斷物件是否相同, 如果相同則會將結果與之前的結果進行合併。

那麼, 結果是否相同是如何判斷的呢?

首先是通過 id的比較, 如果 id 沒有配置則比較每一個欄位(此時, 只要有一個欄位不同的物件不同)

2.1.5.2 id 的作用

在生成的 XML 中, 如果我們有主鍵, 一般會生成一個對應的 id 屬性。

id 屬性就可以用來判斷獲取到的資料是否屬於同一個物件

在以上的例子中, 資料庫中查詢出來有三個 id=1, 則 mybatis 在處理結果時就可以知道這三條資料對應相同的物件, 從而將他們合併。

2.1.5.3 id 的作用驗證

我們可以測試一下, 將 BlogMapper.xmlBaseResultMap 改成 titleid, 然後將資料庫改一下

更改後的資料

那麼, 此時部落格 id=1 和 部落格 id=2title 都為 “小明的部落格”, 那麼, 按照以上的推論, 此兩部落格的物件應該合二為一。

真相揭曉

更改後的結果

小王的部落格消失了, 小明的部落格發表文章數量從 3 變為 5 , 因為合併了小王部落格釋出的文章數量。

2.1.5.4 建議

建議儘量配置 id 的屬性, 如果沒有這個屬性, 則 mybatis 在進行結果合併時效率會低很多。

2.2 collection 巢狀查詢方式

2.2.1 建立結果實體類

我們需要建立一個 BlogPostBO,

public class BlogPostBO extends Blog {

    private List<Post> posts;

    public List<Post> getPosts() {
        return posts;
    }

    public void setPosts(List<Post> posts) {
        this.posts = posts;
    }
}

該類繼承於 Blog 類, 並多了一個連結串列成員變數 posts, 我們後續獲取到的釋出的文章都在此連結串列中。

2.2.2 建立結果集

結果集中多了一個 select 的屬性, 同時, 需要相應的指定 postscolumn

<resultMap id="BlogPostCustom"  type="com.homejim.mybatis.entity.BlogPostBO" extends="BaseResultMap">
    <collection property="posts" column="{blogId=id}"
    select="com.homejim.mybatis.mapper.PostMapper.selectPostByBlogId" fetchType="lazy" />
</resultMap>

重要說明:

  1. column 中, 需要填寫 {屬性名=列名(別名)} 的方式(如果只有一個引數, 也可以直接填列名傳入引數的即可)。 如果是傳遞多個引數, 則是 {屬性名1=列名1, 屬性名2=列名2}

  2. select 屬性值 com.homejim.mybatis.mapper.PostMapper.selectPostByBlogId, 對應的 parameterTypejava.util.Map(也可以不填)。

  3. fetchType 的屬性值 lazy(延遲載入, 還需要相應的配置) 或 eager。(更詳細的參考我上篇文章[mybatis-高階結果對映之一對一]

2.2.3 建立對應的方法和XML

主方法

    /**
     * 獲取部落格及其釋出的文章內容 一對多:延遲載入
     * @return
     */
    List<BlogPostBO> selectBlogAndPostListLazy();

主方法對應的 XML

<select id="selectBlogAndPostListLazy"  resultMap="BlogPostCustom">
    SELECT
      b.id,
      b.title,
      b.author_id
    FROM blog b
</select>

巢狀方法

    /**
     *  根據部落格 id 獲取釋出的文章資訊
     * @param blogId 部落格 id
     * @return
     */
    List<Post> selectPostByBlogId(int blogId);

巢狀方法對應 XML

  <select id="selectPostByBlogId" parameterType="java.util.Map" resultMap="ResultMapWithBLOBs">
    select
    <include refid="Base_Column_List" />
    ,
    <include refid="Blob_Column_List" />
    from post
    where blog_id = #{blogId,jdbcType=INTEGER}
  </select>

2.2.4 測試

    /**
     *  resultMap + collection 一對多對映, 巢狀查詢
     */
    @Test
    public void testSelectBlogAndPostListByBlogLazy() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
        List<BlogPostBO> blogPostBOs = blogMapper.selectBlogAndPostListLazy();

        for (int i = 0; i < blogPostBOs.size(); i++) {
            BlogPostBO blogPostBO = blogPostBOs.get(i);
            System.out.println(blogPostBO.getTitle());
            List<Post> posts = blogPostBO.getPosts();

            for (int j = 0; j < posts.size(); j++) {
                System.out.println(blogPostBO.getPosts().get(j).getContent());
            }

            System.out.println("=============這是物件分割線===============");
        }
    }

延遲載入結果

可以看到, 只有在需要使用物件的時候, 才會進行延遲載入。

3 程式碼

我的 Github:mybatis-mapping