1. 程式人生 > 其它 >MyBatis的多表查詢筆記

MyBatis的多表查詢筆記

MyBatis的多表查詢

隨著學習的進步,需求的提高,我們在實際開發中用的最多的還是多表查詢,就讓我們一起學習MyBatis中的多表查詢。

資料庫準備

Class表

Student表

專案結構

這次使用的是Spring+MyBatis整合的,具體的規範我也不是很清楚,所以並不清楚專案結構是否規範,最終專案結構以實際需求大綱為主。

  • Dao層

    • ClazzMapper、StudentMapper作為MyBatis的*Mapper.xml的介面
    • Tool為提供Service層服務的介面,ToolImpl為該介面的實現類
  • POJO

    • 兩個表的實體類,並根據業務需求添加了額外的屬性。

並不知道需不需要Tool這個結構,學習階段沒有去實際開發過專案,自己感覺需要吧。。。為service提供服務(假設是個web專案)。


我們需要用MyBatis實現多表查詢的方法主要有兩種:業務程式碼實現、SQL語句實現
多表查詢一般有幾種關係?多表查詢分為3種關係:

  • 一對一
  • 一對多
  • 多對多

我們在這裡不討論多對多,因為需要第三方表才能實現。我們討論前兩種!

業務需求:

  • 查詢所有學生對應的班級資訊(一對一)
  • 查詢每個班級中所在學生的資訊(一對多)

1.業務程式碼實現

業務程式碼實現分兩種:一種是使用resultType,另一種是使用resultMap,其實這兩者說的是Mapper.xml檔案中的操作語句的標籤屬性名。

1.1 使用resultType實現業務程式碼的多表查詢:

其實在我們學習MyBatis的時候resultType我們經常使用到(如果忘記了可以百度),其作用就是表明查詢語句中使用什麼資料型別來接收查詢到的資料。

什麼是業務程式碼實現MyBatis的多表查詢?
業務程式碼就是用程式碼實現的查詢,有時候多表查詢並不一定是需要外來鍵連線,兩張表並沒有外交連線,但是需求可能就需要結合兩張表,我們可以用程式碼去控制查詢時表與表之間的關係。具體怎麼操作呢?假如我們現在要實現第一個業務需求(一對一),先寫兩條查詢語句:

  <!-- 根據cid條件查詢t_class的資訊 -->
  <select id="findClass" resultType="clazz">
      select * from t_class where cid = #{parma1}
  </select>

  <!-- 查詢t_student的所有資訊 -->
  <select id="findAll" resultType="student">
      select * from t_student
  </select>

我們可以先將所有學生資訊查詢出來,因為t_student的croom就t_class表中的cid欄位名,然後再用for迴圈將每個學生的croom作為條件,查詢croom對應的班級資訊,再存入Student的Clazz屬性,最後輸出即可。

@Override
    public List<Student> findAll() {
        List<Student> all = studentMapper.findAll();
        for (Student s:all){
            Clazz aClass = clazzMapper.findClass(s.getcRoom());
            s.setLi(aClass);
        }
        return all;
    }

這樣我們就實現了業務程式碼的多表查詢,特點是沒有用到連線查詢,我們每個查詢語句都是單表查詢,但是我們利用了java業務程式碼結合查詢語句實現了多表查詢。
輸出結果:

- ==>  Preparing: select * from t_student 
- ==> Parameters: 
- <==      Total: 7
- ==>  Preparing: select * from t_class where cid = ? 
- ==> Parameters: 4(Integer)
- <==      Total: 1
- ==>  Preparing: select * from t_class where cid = ? 
- ==> Parameters: 4(Integer)
- <==      Total: 1
- ==>  Preparing: select * from t_class where cid = ? 
- ==> Parameters: 5(Integer)
- <==      Total: 1
- ==>  Preparing: select * from t_class where cid = ? 
- ==> Parameters: 1(Integer)
- <==      Total: 1
- ==>  Preparing: select * from t_class where cid = ? 
- ==> Parameters: 2(Integer)
- <==      Total: 1
- ==>  Preparing: select * from t_class where cid = ? 
- ==> Parameters: 3(Integer)
- <==      Total: 1
- ==>  Preparing: select * from t_class where cid = ? 
- ==> Parameters: 3(Integer)
- <==      Total: 1
Student{sid=2, sName='zhangsan', sex='男', age=21, cRoom=4, li=Clazz{cid=4, cName='JAVA005', room='r504', li=null}}
Student{sid=4, sName='lisi', sex='男', age=23, cRoom=4, li=Clazz{cid=4, cName='JAVA005', room='r504', li=null}}
Student{sid=5, sName='lisi', sex='男', age=21, cRoom=5, li=Clazz{cid=5, cName='PYTHON001', room='r505', li=null}}
Student{sid=6, sName='wangwu', sex='男', age=35, cRoom=1, li=Clazz{cid=1, cName='JAVA002', room='r501', li=null}}
Student{sid=7, sName='zhaoliu', sex='男', age=45, cRoom=2, li=Clazz{cid=2, cName='JAVA003', room='r502', li=null}}
Student{sid=8, sName='zhaoliu', sex='男', age=18, cRoom=3, li=Clazz{cid=3, cName='JAVA004', room='r503', li=null}}
Student{sid=9, sName='tome', sex='男', age=33, cRoom=3, li=Clazz{cid=3, cName='JAVA004', room='r503', li=null}}

我們可以看到日誌輸出一共有7+1(8)條查詢記錄。這種方法我們是通過用java程式碼實現兩張表的關係,所以執行的sql語句有8條

1.2 使用resultMap實現多表查詢:

<!-- 使用resultMap實現多表查詢 -->
<select id="findAll" resultMap="rm1">
    select * from t_student
</select>

<!-- 根據cid條件查詢t_class的資訊(namespace:com.lyl.dao.ClazzMapper) -->
<select id="findClass" resultType="clazz">
    select * from t_class where cid = #{parma1}
</select>

<!-- student:接收的資料型別 -->
<resultMap id="rm1" type="student">
    <!-- id為該表的主鍵,需要指出 -->
    <id column="sid" property="id"/>
    <!-- 
      result標籤:column屬性表示欄位名,property屬性表示實體類的屬性名。
      因為我們使用的sql語句是單表查詢查詢出的欄位名,MyBatis會自動幫我們賦值,
      前提是我們實體類的屬性名要和表的欄位名一一對應,否則就必須手動指明賦值
    -->
    <result column="sName" property="sName"/>
    <result column="sex" property="sex"/>
    <result column="age" property="age"/>
    <result column="cRoom" property="cRoom"/>
    <!-- 
      association標籤:當屬性型別是單個物件,我們就需要使用該標籤來將資料傳遞給實體類。
      select屬性:執行的sql語句,Mapper.xml的namespace加上id
      column屬性:資料表的列名或者標籤別名,比如我們傳遞t_student的croom的值給select的sql語句,MyBatis就會將t_student指定的欄位名的值傳遞給select的sql語句。
      我們也可以理解為呼叫了select的sql語句,croom作為引數傳遞給:
       select * from t_class where cid = #{parma1}

      注意:column屬性賦值時應該和最終查詢到的結果集中的列名對應!!!

      javaType屬性:指定select的sql語句執行完畢返回的結果集的資料型別(clazz)
      property屬性:將最終處理後的結果傳遞給指定的實體類屬性
    -->
    <association select="com.lyl.dao.ClazzMapper.findClass" column="cRoom" javaType="clazz" property="li"/>
</resultMap>

業務程式碼:

@Override
  public List<Student> findAll() {
      return studentMapper.findAll();
  }

檢視日誌輸出結果:

- ==>  Preparing: select * from t_student 
- ==> Parameters: 
- ====>  Preparing: select * from t_class where cid = ? 
- ====> Parameters: 4(Integer)
- <====      Total: 1
- ====>  Preparing: select * from t_class where cid = ? 
- ====> Parameters: 5(Integer)
- <====      Total: 1
- ====>  Preparing: select * from t_class where cid = ? 
- ====> Parameters: 1(Integer)
- <====      Total: 1
- ====>  Preparing: select * from t_class where cid = ? 
- ====> Parameters: 2(Integer)
- <====      Total: 1
- ====>  Preparing: select * from t_class where cid = ? 
- ====> Parameters: 3(Integer)
- <====      Total: 1
- <==      Total: 7
Student{sid=2, sName='zhangsan', sex='男', age=21, cRoom=4, li=Clazz{cid=4, cName='JAVA005', room='r504', li=null}}
Student{sid=4, sName='lisi', sex='男', age=23, cRoom=4, li=Clazz{cid=4, cName='JAVA005', room='r504', li=null}}
Student{sid=5, sName='lisi', sex='男', age=21, cRoom=5, li=Clazz{cid=5, cName='PYTHON001', room='r505', li=null}}
Student{sid=6, sName='wangwu', sex='男', age=35, cRoom=1, li=Clazz{cid=1, cName='JAVA002', room='r501', li=null}}
Student{sid=7, sName='zhaoliu', sex='男', age=45, cRoom=2, li=Clazz{cid=2, cName='JAVA003', room='r502', li=null}}
Student{sid=8, sName='zhaoliu', sex='男', age=18, cRoom=3, li=Clazz{cid=3, cName='JAVA004', room='r503', li=null}}
Student{sid=9, sName='tome', sex='男', age=33, cRoom=3, li=Clazz{cid=3, cName='JAVA004', room='r503', li=null}}

結果是執行的查詢記錄有7+1(8)條。我們這次並沒有用上面的業務程式碼實現多表查詢,而是藉助了MyBatis的標籤——resultMap實現了多表查詢。resultMap標籤的屬性介紹都寫在了Mapper.xml檔案中。

結合上訴兩種方式小結:

  • 兩種方式查詢的結果是一樣的,並且都查詢了7+1條語句,7條執行班級的條件查詢,1條執行學生的所有查詢,簡稱:"N+1"方式的多表查詢,先查詢出某個表的全部資訊,根據這個表的資訊查詢出另一個表的資訊。
  • 兩者都是業務程式碼實現多表查詢,總的來說就是將平常的連線查詢語句拆成了多個單表查詢,用來實現,雖然業務程式碼多了一點,但是相對於用一條SQL執行多表查詢來說某種程度降低了難度,比如:用一條SQL實現多表查詢,如果需求很複雜,這個時候我們可以將其部分拆解為單表查詢結合起來,只要適當合理,不僅僅可以降低難度,也可以方便維護。
  • 區分resultMap和resultType的區別:
    • resultType:很簡單,就是定義返回結果值的型別
    • resultMap:推薦還是看官方文件吧,我才學疏淺總結的不好怕帶歪,總的來說resultMap也是MyBatis的很重要的核心之一,因為官方就是這樣形容的:resultMap元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90% 的 JDBC ResultSets 資料提取程式碼中解放出來,並在一些情形下允許你進行一些 JDBC 不支援的操作
      上面這兩種就是使用業務裝配的方式實現了多表查詢(多個單表查詢實現多表查詢)

2.SQL語句實現

既然是多表查詢,那麼肯定就有用一條SQL語句查詢實現多表查詢的。
直接上程式碼:


<select id="findStu1" resultMap="rm3">
    select * from t_student s join t_class c on s.croom = c.cid
</select>

<resultMap id="rm3" type="student">
    <!-- 將資料庫的欄位名中的值賦值給實體類的屬性 -->
    <id column="sid" property="id"/>
    <result column="sName" property="sName"/>
    <result column="sex" property="sex"/>
    <result column="age" property="age"/>
    <result column="cRoom" property="cRoom"/>
    <!--
        單個物件(association)賦值:
        property:實體類中物件的屬性名
        javaType:要賦值的單個物件的型別
        此時association不再有select標籤,而是findStu1的sql語句已經將所有結果查詢出來,我們只要進行一個自定義賦值即可,手動將需要的資料從resultMap中拿出賦值給實體類的屬性
      -->


    <!-- 
      將資料資料賦值給clazz物件,並將結果集賦值給Student實體類的li屬性 
    -->
    <association property="li" javaType="clazz">
        <id column="cid" property="cid"/>
        <result column="cName" property="cName"/>
        <result column="room" property="room"/>
    </association>
</resultMap>

再來看看我們呼叫的結果:

- ==>  Preparing: select * from t_student s join t_class c on s.croom = c.cid 
- ==> Parameters: 
- <==      Total: 7
Student{sid=2, sName='zhangsan', sex='男', age=21, cRoom=4, li=Clazz{cid=4, cName='JAVA005', room='r504', li=null}}
Student{sid=4, sName='lisi', sex='男', age=23, cRoom=4, li=Clazz{cid=4, cName='JAVA005', room='r504', li=null}}
Student{sid=5, sName='lisi', sex='男', age=21, cRoom=5, li=Clazz{cid=5, cName='PYTHON001', room='r505', li=null}}
Student{sid=6, sName='wangwu', sex='男', age=35, cRoom=1, li=Clazz{cid=1, cName='JAVA002', room='r501', li=null}}
Student{sid=7, sName='zhaoliu', sex='男', age=45, cRoom=2, li=Clazz{cid=2, cName='JAVA003', room='r502', li=null}}
Student{sid=8, sName='zhaoliu', sex='男', age=18, cRoom=3, li=Clazz{cid=3, cName='JAVA004', room='r503', li=null}}
Student{sid=9, sName='tome', sex='男', age=33, cRoom=3, li=Clazz{cid=3, cName='JAVA004', room='r503', li=null}}

可以看到結果是一樣的,但是執行的sql語句只有一句。這就是用SQL語句實現多表查詢,特點就是用了連線查詢。用的依然是resultMap,可以看出resultMap有多重要了!

總結:

  1. 上訴介紹了3中不同的方式實現多表查詢,多數使用第二種和第三種,甚至可能搭配使用。
  2. 在第二種方式中如果實體類的屬性名與查詢結果最終的欄位名相同,MyBatis可以幫我們自動對映到實體類中,如果查詢時設定了別名,就必須用result標籤手動指定賦值。
  3. 第三章方式中就不能省略result標籤,你想從resultMap取什麼值就必須用result標籤表明,如果省略不寫,MyBatis不會自動幫我們裝配,所以我們必須指明。注意:如果兩張表出現了相同欄位名,我們必須在SQL語句中使用別名將他們區分,否則MyBatis會以靠前的欄位名資料賦值。
  4. 既然單個物件賦值欄位名是association,那麼如果是一對多中的集合物件呢?我們怎麼做?這個時候我們就需要換一個標籤——collection,並且將javaType屬性換成ofType屬性名即可,ofType的屬性我們可以看作是集合的泛型型別,其他的用法一樣,我們這一點必須要區分取開來。
  5. 如果我們使用了resultMap,我們就要將select中的resultType換成resultMap,且兩者不能共存。

更多的我們應該結合官方部落格的部分參考來幫助我們學習理解!