Mybatis高級查詢之關聯查詢


learn from:http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html#Result_Maps

關聯查詢

  1. 准備
  2. 關聯結果查詢(一對一)
  3. resultMap復用
  4. 集合(一對多)

3 關聯查詢

做查詢之前,先修改幾個配置。mapper.xml是在mybatis-config.xml中指定,那么我們每增加一個mapper都要增加一個配置,很麻煩。為了簡化配置。需要將mapper接口和mapper.xml放到同一個文件下,並且接口和xml文件命名一致。使用mybatis的自動掃描:.這樣,當我們新增接口的時候,直接創建接口和對應xml文件就可以了:

<mappers>
        <!--<mapper resource="com.test.mapper.dao/AuthorMapper.xml"/>-->
        <package name="com.test.mapper.dao"/>
</mappers>

  

3.1 prepare

增加一個表blog :

CREATE TABLE `blog` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `author_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8; 

創建實體類com.test.mapper.model.Blog:

package com.test.mapper.model;

/**
 * Created by miaorf on 2016/7/20.
 */
public class Blog {
    private Integer id;
    private String name;
    private Author author;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

    @Override
    public String toString() {
        return "Blog{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", author=" + author +
                '}';
    }
}

創建接口:com/test/mapper/dao/BlogMapper.xml

package com.test.mapper.dao;

import com.test.mapper.model.Blog;

import java.util.List;

/**
 * Created by miaorf on 2016/7/20.
 */
public interface BlogMapper {

    List<Blog> selectBlog(Integer id);
}
 
        

 創建xml:com/test/mapper/dao/BlogMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.test.mapper.dao.BlogMapper">
    <resultMap id="blogResult" type="blog">
        <association property="author" column="author_id" javaType="author" select="com.test.mapper.dao.AuthorMapper.selectAuthorById"/>
    </resultMap>
    <select id="selectBlog" resultMap="blogResult">
        select * from blog where id = #{id}
    </select>

</mapper>

 

3.2含義

首先,修改了mybatis配置文件中mapper掃描配置,因此可以直接在掃描包下添加接口和xml文件。

其次,關於mybatis的命名空間namespace的用法,這個是唯一的,可以代表這個xml文件本身。因此,當我想要引用Author的查詢的時候,我可以直接使用AuthorMapper.xml的命名空間點select的id來唯一確定select片段。即:

select="com.test.mapper.dao.AuthorMapper.selectAuthorById"

然后,關聯查詢,blog的author_id字段和author的id字段關聯。因此,將Author作為Blog的一個屬性,先查詢blog,然后根據author_id字段去查詢author。也就是說,查詢了兩次。這里也是我困惑的地方,為什么mybatis要這樣處理,命名可以一次查詢取得數據非要兩次查詢。

注意:association節點中

  • column字段是author_id,是當做參數傳遞給查詢Author的查詢語句的,如果查詢語句的參數有多個則:column= ” {prop1=col1,prop2=col2} ” 這種語法來傳遞給嵌套查詢語 句。這會引起 prop1 和 prop2 以參數對象形式來設置給目標嵌套查詢語句。
  • property則表示在Blog類中對應的屬性。

 

我們有兩個查詢語句:一個來加載博客,另外一個來加載作者,而且博客的結果映射描 述了“selectAuthor”語句應該被用來加載它的 author 屬性。

其他所有的屬性將會被自動加載,假設它們的列和屬性名相匹配。

這種方式很簡單, 但是對於大型數據集合和列表將不會表現很好。 問題就是我們熟知的 “N+1 查詢問題”。概括地講,N+1 查詢問題可以是這樣引起的:

  • 你執行了一個單獨的 SQL 語句來獲取結果列表(就是“+1”)。
  • 對返回的每條記錄,你執行了一個查詢語句來為每個加載細節(就是“N”)。

這個問題會導致成百上千的 SQL 語句被執行。這通常不是期望的。

MyBatis 能延遲加載這樣的查詢就是一個好處,因此你可以分散這些語句同時運行的消 耗。然而,如果你加載一個列表,之后迅速迭代來訪問嵌套的數據,你會調用所有的延遲加 載,這樣的行為可能是很糟糕的。

所以還有另外一種方法

 

3.3 關聯查詢結果

上述關聯查詢主要是為了延遲加載,做緩存用的,如果你不調用blog.getAuthor()來獲取author,那么mybatis就不會去查詢Author。也就是說,mybatis把博客和作者的查詢當做兩步來執行。實現信息分段加載,在某些場合是有用的。然而在博客這里,顯然不太合適,因為我們看到博客的同時都會看到作者,那么必然會導致查詢數據庫兩次。下面,來測試另一種方式,像sql關聯查詢一樣,一次查出結果。

<select id="selectBlogWithAuthor" resultMap="blogResultWithAuthor">
        SELECT
          b.id        as blog_id,
          b.name      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>
    <resultMap id="blogResultWithAuthor" type="blog">
        <id property="id" column="blog_id"/>
        <result property="name" column="blog_title"/>
        <association property="author" column="blog_author_id" 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>

和3.1里的查詢一樣,都是查詢出blog和Author。但這個只查詢數據庫一次,也就是說實現了我們的關聯查詢。這幾行代碼乍一看有點復雜,仔細分析一下就很明了了。

1> 首先看到的是select標簽,這個表示查詢。其中id表示對應的接口的方法名;resultMap的值是一個resultMap節點的id,這個表示select查詢結果的映射方式。

2> select中間就是我熟悉的關聯查詢語句,這里不做贅述

3> 然后就是resultMap所指向的節點blogResultWithAuthor。

  • resultMap節點的id就是唯一標識這個節點的,type表示這個resultMap最終映射成的實體類Blog。
  • id是主鍵映射,這個和mybatis緩存有關。
  • result:
  • association:
  • authorResult同理。

可以看到控制台打印的日志:

2016-07-22 23:01:00,148 DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Opening JDBC Connection
2016-07-22 23:01:11,017 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Created connection 1893960929. 2016-07-22 23:01:19,281 DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@70e38ce1] 2016-07-22 23:01:27,018 DEBUG [com.test.mapper.dao.BlogMapper.selectBlogWithAuthor] - ==> Preparing: SELECT b.id as blog_id, b.name 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 = ? 2016-07-22 23:01:29,697 DEBUG [com.test.mapper.dao.BlogMapper.selectBlogWithAuthor] - ==> Parameters: 1(Integer) 2016-07-22 23:01:30,091 DEBUG [com.test.mapper.dao.BlogMapper.selectBlogWithAuthor] - <== Total: 1

 

3.4 resultMap復用

只要引用resultMap的id就可以引用這個resultMap。然而,有時候resultMap的結構相同,但字段不同。比如,Blog有兩個作者Author、co-Athour。這時候的查詢語句是這樣的:

SELECT
        b.id        as blog_id,
        b.name      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,
        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}

顯然合作者和作者的結構式相同的,但是我們給了不同的別名(alias)。這時候可以使用resultMap的columnPrefix屬性:

<!-- association 2 -->
    <select id="selectBlogWithAuthor2" resultMap="blogResultWithAuthor2">
        SELECT
        b.id        as blog_id,
        b.name      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,
        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>
    <resultMap id="blogResultWithAuthor2" type="Blog">
        <id property="id" column="blog_id"/>
        <result property="name" column="blog_title"/>
        <association property="author" resultMap="authorResult"/>
        <association property="coAuthor" resultMap="authorResult"
                     columnPrefix="co_"/>
    </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>
View Code

 

 3.4集合

上述關聯查詢是has a關系,一對一的。一對多的,需要使用collection。

比如,一個博客有一個作者,但有很多博文。

創建博文表post:

/*
Navicat MySQL Data Transfer

Source Server         : localhost
Source Server Version : 50605
Source Host           : localhost:3306
Source Database       : mybatis

Target Server Type    : MYSQL
Target Server Version : 50605
File Encoding         : 65001

Date: 2016-07-24 22:39:35
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for post
-- ----------------------------
DROP TABLE IF EXISTS `post`;
CREATE TABLE `post` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `subject` varchar(255) DEFAULT NULL,
  `body` varchar(255) DEFAULT NULL,
  `blog_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of post
-- ----------------------------
INSERT INTO `post` VALUES ('1', 'test', 'test-body', '1');
View Code

 

創建博文實體類com.test.mapper.model.Post:

package com.test.mapper.model;

/**
 * Created by miaorf on 2016/7/24.
 */
public class Post {
    private Integer id;
    private String subject;
    private String body;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}
View Code

在Blog里添加屬性字段

private List<Post> posts;

 

編寫查詢如下:

<!-- collection -->
    <select id="selectBlogWithPost" resultMap="blogResultWithPost">
        SELECT
        b.id    AS   blog_id,
        b.name  AS   blog_name,
        b.author_id AS blog_author_id,
        p.id        AS post_id,
        p.subject   AS post_subject,
        p.body      AS post_body
        FROM blog b
        LEFT OUTER JOIN post p ON b.id=p.blog_id
        WHERE b.id=#{id}
    </select>
    <resultMap id="blogResultWithPost" type="Blog">
        <id property="id" column="blog_id"/>
        <result property="name" column="blog_name"/>
        <collection property="posts" ofType="Post">
            <id property="id" column="post_id"/>
            <result property="subject" column="post_subject"/>
            <result property="body" column="post_body"/>
        </collection>
    </resultMap>

 很容易看出來,集合和關聯幾乎完全一樣,只不過一個是association一個是collection,一個是一對一,一個是一對多。到這里基本就可以滿足所有的sql關聯查詢了。關於collection的參數,還是要注釋一下:

 

同樣的,集合collection和關聯association一樣,都屬於resultMap類型,即結果集映射規則。那么,collection也可以抽出來復用:

<resultMap id="blogResultWithPost" type="Blog">
        <id property="id" column="blog_id"/>
        <result property="name" column="blog_name"/>
        <collection property="posts" ofType="Post" resultMap="postResult" columnPrefix="post_"/>
</resultMap>
<resultMap id="postResult" type="Post">
    <id property="id" column="id"/>
    <result property="subject" column="subject"/>
    <result property="body" column="body"/>
</resultMap>

 注意 

這個對你所映射的內容沒有深度,廣度或關聯和集合相聯合的限制。當映射它們 時你應該在大腦中保留它們的表現。 你的應用在找到最佳方法前要一直進行的單元測試和性 能測試。好在 myBatis 讓你后來可以改變想法,而不對你的代碼造成很小(或任何)影響。

高級關聯和集合映射是一個深度的主題。文檔只能給你介紹到這了。加上一點聯系,你 會很快清楚它們的用法

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM