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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章