天天看點

mybatis-進階結果映射之一對多

@

目錄

  • 1 資料準備
    • 1.2 實體類, 接口和XML
  • 2 一對多映射
    • 2.1 collection集合映射
      • 2.1.1 建立結果實體類
      • 2.1.2 建立結果集
      • 2.1.3 建立對應的方法和XML
      • 2.1.4 測試
      • 2.1.5 結果合并原理
        • 2.1.5.1 合并的依據
        • 2.1.5.2 id 的作用
        • 2.1.5.3 id 的作用驗證
        • 2.1.5.4 建議
    • 2.2 collection 嵌套查詢方式
      • 2.2.1 建立結果實體類
      • 2.2.2 建立結果集
      • 2.2.3 建立對應的方法和XML
      • 2.2.4 測試
  • 3 代碼
  • 一起學 mybatis

在一對多的關系中, 主表的資料回對應關聯表中的多條資料。 是以, 查詢時就會查詢出多條結果, 是以, 向類似的情況我們會使用

List

來進行存儲關聯表中擷取到的資訊。

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

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

使用 mybatis-代碼生成器 生成相應的實體類, 接口和XML。

以上為生成的項目結構。

我們需要建立一個

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

, 我們後續擷取到的釋出的文章都在此連結清單中。

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

<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>
           

可以簡單了好多。

首先就是在對應的 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>
           

/**
     *  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("=============這是對象分割線===============");
        }
    }
           

測試結果

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

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

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

mybatis 在處理結果時, 會判斷對象是否相同, 如果相同則會将結果與之前的結果進行合并。

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

首先是通過 id的比較, 如果 id 沒有配置則比較每一個字段(此時, 隻要有一個字段不同的對象不同)。

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

而 id 屬性就可以用來判斷擷取到的資料是否屬于同一個對象。

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

我們可以測試一下, 将 BlogMapper.xml 的 BaseResultMap 改成 title 為 id, 然後将資料庫改一下

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

真相揭曉

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

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

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

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

<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

    , 對應的 parameterType 是

    java.util.Map

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

主方法

/**
     * 擷取部落格及其釋出的文章内容 一對多:延遲加載
     * @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>
           

/**
     *  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("=============這是對象分割線===============");
        }
    }
           

可以看到, 隻有在需要使用對象的時候, 才會進行延遲加載。

我的 Github:mybatis-mapping

你想不想來學習 mybatis? 學習其使用和源碼呢?那麼, 在部落格園關注我吧!!

我自己打算把這個源碼系列更新完畢, 同時會更新相應的注釋。快去 star 吧!!

mybatis最新源碼和注釋

mybatis-進階結果映射之一對多

作者:阿進的寫字台

出處:https://www.cnblogs.com/homejim/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。