執行個體講解Java的MyBatis架構對MySQL中資料的關聯查詢_java

來源:互聯網
上載者:User

mybatis 提供了進階的關聯查詢功能,可以很方便地將資料庫擷取的結果集映射到定義的Java Bean 中。下面通過一個執行個體,來展示一下Mybatis對於常見的一對多和多對一關聯性複雜映射是怎樣處理的。
設計一個簡單的部落格系統,一個使用者可以開多個部落格,在部落格中可以發表文章,允許發表評論,可以為文章加標籤。部落格系統主要有以下幾張表構成:
Author表:作者資訊表,記錄作者的資訊,使用者名稱和密碼,郵箱等。
Blog表   :  部落格表,一個作者可以開多個部落格,即Author和Blog的關係是一對多。
Post表  : 文章記錄表,記錄文章發表時間,標題,本文等資訊;一個部落格下可以有很多篇文章,Blog 和Post的關係是一對多。
Comments表:文章評論表,記錄文章的評論,一篇文章可以有很多個評論:Post和Comments的對應關係是一對多。
Tag表:標籤表,表示文章的標籤分類,一篇文章可以有多個標籤,而一個標籤可以應用到不同的文章上,所以Tag和Post的關係是多對多的關係;(Tag和Post的多對多關係通過Post_Tag表體現)
Post_Tag表: 記錄 文章和標籤的對應關係。

一般情況下,我們會根據每一張表的結構 建立與此相對應的JavaBean(或者Pojo),來完成對錶的基本CRUD操作。

上述對單個表的JavaBean定義有時候不能滿足業務上的需求。在業務上,一個Blog對象應該有其作者的資訊和一個文章列表,如下圖所示:

如果想得到這樣的類的執行個體,則最起碼要有一下幾步:
1. 通過Blog 的id 到Blog表裡查詢Blog資訊,將查詢到的blogId 和title 賦到Blog對象內;
2. 根據查詢到到blog資訊中的authorId 去 Author表擷取對應的author資訊,擷取Author對象,然後賦到Blog對象內;
3. 根據 blogId 去 Post表裡查詢 對應的 Post文章列表,將List<Post>對象賦到Blog對象中;
這樣的話,在底層最起碼調用三次查詢語句,請看下列的代碼:

/*  * 通過blogId擷取BlogInfo對象  */ public static BlogInfo ordinaryQueryOnTest(String blogId) {  BigDecimal id = new BigDecimal(blogId);  SqlSession session = sqlSessionFactory.openSession();  BlogInfo blogInfo = new BlogInfo();  //1.根據blogid 查詢Blog對象,將值設定到blogInfo中  Blog blog = (Blog)session.selectOne("com.foo.bean.BlogMapper.selectByPrimaryKey",id);  blogInfo.setBlogId(blog.getBlogId());  blogInfo.setTitle(blog.getTitle());    //2.根據Blog中的authorId,進入資料庫查詢Author資訊,將結果設定到blogInfo對象中  Author author = (Author)session.selectOne("com.foo.bean.AuthorMapper.selectByPrimaryKey",blog.getAuthorId());  blogInfo.setAuthor(author);    //3.查詢posts對象,設定進blogInfo中  List posts = session.selectList("com.foo.bean.PostMapper.selectByBlogId",blog.getBlogId());  blogInfo.setPosts(posts);  //以JSON字串的形式將對象列印出來  JSONObject object = new JSONObject(blogInfo);  System.out.println(object.toString());  return blogInfo; } 

從上面的代碼可以看出,想擷取一個BlogInfo對象比較麻煩,總共要調用三次資料庫查詢,得到需要的資訊,然後再組裝BlogInfo對象。

嵌套語句查詢
mybatis提供了一種機制,叫做嵌套語句查詢,可以大大簡化上述的操作,加入配置及代碼如下:

<resultMap type="com.foo.bean.BlogInfo" id="BlogInfo">  <id column="blog_id" property="blogId" />  <result column="title" property="title" />  <association property="author" column="blog_author_id"   javaType="com.foo.bean.Author" select="com.foo.bean.AuthorMapper.selectByPrimaryKey">  </association>  <collection property="posts" column="blog_id" ofType="com.foo.bean.Post"   select="com.foo.bean.PostMapper.selectByBlogId">  </collection> </resultMap>  <select id="queryBlogInfoById" resultMap="BlogInfo" parameterType="java.math.BigDecimal">  SELECT  B.BLOG_ID,  B.TITLE,  B.AUTHOR_ID AS BLOG_AUTHOR_ID  FROM LOULUAN.BLOG B  where B.BLOG_ID = #{blogId,jdbcType=DECIMAL} </select> 
/*  * 通過blogId擷取BlogInfo對象  */ public static BlogInfo nestedQueryOnTest(String blogId) {  BigDecimal id = new BigDecimal(blogId);  SqlSession session = sqlSessionFactory.openSession();  BlogInfo blogInfo = new BlogInfo();  blogInfo = (BlogInfo)session.selectOne("com.foo.bean.BlogMapper.queryBlogInfoById",id);  JSONObject object = new JSONObject(blogInfo);  System.out.println(object.toString());  return blogInfo; } 

通過上述的代碼完全可以實現前面的那個查詢。這裡我們在代碼裡只需要 blogInfo = (BlogInfo)session.selectOne("com.foo.bean.BlogMapper.queryBlogInfoById",id);一句即可擷取到複雜的blogInfo對象。

嵌套語句查詢的原理
在上面的代碼中,Mybatis會執行以下流程:
1.先執行 queryBlogInfoById 對應的語句從Blog表裡擷取到ResultSet結果集;
2.取出ResultSet下一條有效記錄,然後根據resultMap定義的映射規格,通過這條記錄的資料來構建對應的一個BlogInfo 對象。
3. 當要對BlogInfo中的author屬性進行賦值的時候,發現有一個關聯的查詢,此時Mybatis會先執行這個select查詢語句,得到返回的結果,將結果設定到BlogInfo的author屬性上;
4. 對BlogInfo的posts進行賦值時,也有上述類似的過程。
5. 重複2步驟,直至ResultSet. next () == false;
以下是blogInfo物件建構賦值過程示意圖:

這種關聯的巢狀查詢,有一個非常好的作用就是:可以重用select語句,通過簡單的select語句之間的組合來構造複雜的對象。上面嵌套的兩個select語句com.foo.bean.AuthorMapper.selectByPrimaryKey和com.foo.bean.PostMapper.selectByBlogId完全可以獨立使用。

N+1問題
它的弊端也比較明顯:即所謂的N+1問題。關聯的巢狀查詢顯示得到一個結果集,然後根據這個結果集的每一條記錄進行關聯查詢。
現在假設巢狀查詢就一個(即resultMap 內部就一個association標籤),現查詢的結果集返回條數為N,那麼關聯查詢語句將會被執行N次,加上自身返回結果集查詢1次,共需要訪問資料庫N+1次。如果N比較大的話,這樣的資料庫訪問消耗是非常大的!所以使用這種嵌套語句查詢的使用者一定要考慮謹慎考慮,確保N值不會很大。
以上面的例子為例,select 語句本身會返回com.foo.bean.BlogMapper.queryBlogInfoById 條數為1 的結果集,由於它有兩條關聯的語句查詢,它需要共訪問資料庫 1*(1+1)=3次資料庫。

嵌套結果查詢
嵌套語句的查詢會導致資料庫訪問次數不定,進而有可能影響到效能。Mybatis還支援一種嵌套結果的查詢:即對於一對多,多對多,多對一的情況的查詢,Mybatis通過聯集查詢,將結果從資料庫內一次性查出來,然後根據其一對多,多對一,多對多的關係和ResultMap中的配置,進行結果的轉換,構建需要的對象。
重新定義BlogInfo的結果映射 resultMap

<resultMap type="com.foo.bean.BlogInfo" id="BlogInfo">  <id column="blog_id" property="blogId"/>  <result column="title" property="title"/>  <association property="author" column="blog_author_id" javaType="com.foo.bean.Author">   <id column="author_id" property="authorId"/>   <result column="user_name" property="userName"/>   <result column="password" property="password"/>   <result column="email" property="email"/>   <result column="biography" property="biography"/>  </association>  <collection property="posts" column="blog_post_id" ofType="com.foo.bean.Post">   <id column="post_id" property="postId"/>   <result column="blog_id" property="blogId"/>   <result column="create_time" property="createTime"/>   <result column="subject" property="subject"/>   <result column="body" property="body"/>   <result column="draft" property="draft"/>  </collection>   </resultMap> 

對應的sql語句如下:

<select id="queryAllBlogInfo" resultMap="BlogInfo">  SELECT   B.BLOG_ID,   B.TITLE,   B.AUTHOR_ID AS BLOG_AUTHOR_ID,   A.AUTHOR_ID,   A.USER_NAME,   A.PASSWORD,   A.EMAIL,   A.BIOGRAPHY,   P.POST_ID,   P.BLOG_ID AS BLOG_POST_ID ,  P.CREATE_TIME,   P.SUBJECT,   P.BODY,   P.DRAFT FROM BLOG B LEFT OUTER JOIN AUTHOR A  ON B.AUTHOR_ID = A.AUTHOR_ID LEFT OUTER JOIN POST P  ON P.BLOG_ID = B.BLOG_ID </select> 
/*  * 擷取所有Blog的所有資訊  */ public static BlogInfo nestedResultOnTest() {  SqlSession session = sqlSessionFactory.openSession();  BlogInfo blogInfo = new BlogInfo();  blogInfo = (BlogInfo)session.selectOne("com.foo.bean.BlogMapper.queryAllBlogInfo");  JSONObject object = new JSONObject(blogInfo);  System.out.println(object.toString());  return blogInfo; } 

嵌套結果查詢的執行步驟:
1.根據表的對應關係,進行join操作,擷取到結果集;
2. 根據結果集的資訊和BlogInfo 的resultMap定義資訊,對返回的結果集在記憶體中進行組裝、賦值,構造BlogInfo;
3. 返回構造出來的結果List<BlogInfo> 結果。
對於關聯的結果查詢,如果是多對一的關係,則通過形如 <association property="author" column="blog_author_id" javaType="com.foo.bean.Author"> 進行配置,Mybatis會通過column屬性對應的author_id 值去從記憶體中取資料,並且封裝成Author對象;
如果是一對多的關係,就如Blog和Post之間的關係,通過形如 <collection property="posts" column="blog_post_id" ofType="com.foo.bean.Post">進行配置,MyBatis通過 blog_Id去記憶體中取Post對象,封裝成List<Post>;
對於關連接果的查詢,只需要查詢資料庫一次,然後對結果的整合和組裝全部放在了記憶體中。
以上是通過查詢Blog所有資訊來示範了一對多和多對一的映射對象處理。

ps:自身關聯映射樣本:
實體類

public class Module {   private int id;  private String key;  private String name;  private Module parentModule;  private List<Module> childrenModules;  private String url;  private int sort;  private String show;  private String del;   public int getId() {   return id;  }   public void setId(int id) {   this.id = id;  }   public String getKey() {   return key;  }   public void setKey(String key) {   this.key = key;  }   public String getName() {   return name;  }   public void setName(String name) {   this.name = name;  }   public Module getParentModule() {   return parentModule;  }   public void setParentModule(Module parentModule) {   this.parentModule = parentModule;  }   public String getUrl() {   return url;  }   public void setUrl(String url) {   this.url = url;  }   public int getSort() {   return sort;  }   public void setSort(int sort) {   this.sort = sort;  }   public String getShow() {   return show;  }   public void setShow(String show) {   this.show = show;  }   public String getDel() {   return del;  }   public void setDel(String del) {   this.del = del;  }   public List<Module> getChildrenModules() {   return childrenModules;  }   public void setChildrenModules(List<Module> childrenModules) {   this.childrenModules = childrenModules;  } } 
XML代碼:
<mapper namespace="com.sagaware.caraccess.mapper.ModuleMapper">   <resultMap type="Module" id="moduleResultMap">   <id property="id" column="module_id"/>   <result property="key" column="module_key"/>   <result property="name" column="module_name"/>   <result property="url" column="module_url"/>   <result property="sort" column="module_sort"/>   <result property="show" column="module_show"/>   <result property="del" column="module_del"/>      <!-- 查詢父模組 -->   <association property="parentModule" column="module_parent_id" select="getModulesById" />      <!-- 查詢子模組 -->   <collection property="childrenModules" column="module_id" select="getChildrenModues" />     </resultMap>    <select id="getModules" parameterType="String" resultMap="moduleResultMap">   select * from tb_module where module_id=2  </select>    <select id="getModulesById" parameterType="int" resultMap="moduleResultMap">   select * from tb_module where module_id = #{module_id}  </select>    <select id="getChildrenModues" parameterType="int" resultMap="moduleResultMap">   select * from tb_module where module_parent_id = #{module_id}  </select> </mapper> 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.