MyBatis學習——高級結果映射

前面我們講解了一下關於MyBatis的動態SQL查詢,對MyBatis的基本用法有了大致的瞭解,如果忘記了可以去複習一下,MyBatis學習——動態SQL。今天我們將講解一下MyBatis的高級查詢知識點。

前言:在關係型數據庫中,我們經常要處理一對一 、一對多的關係。在面對這種關係的時候,我們可能要寫多個方法分別查詢這些數據,然後再組合到一起。這種處理方式特別適合用在大型系統上,由於分庫分表,這種用法可以減少表之間的關聯查詢,方便系統進行擴展。但是在一般的企業級應用中,使用MyBatis的高級結果映射便可以輕鬆地處理這種一對一 、 一對多的關係。

比如,現在我有兩張表,一張表示用戶相關信息,另一張表示用戶角色相關信息,一個用戶可以對應多個角色。

用戶表:
user

角色表:
role

下面的內容我們將根據這兩張表用戶與角色的內容進行講解。

一、一對一映射

1、使用自動映射處理一對一關係(對象與數據庫字段映射)

比如,這裏我們使用了role表的查詢,並關聯user表,將我們需要的結果全部都查詢出來,然後再在對應的實體中進行映射,所以在User實體當中,我們對role進行映射。查詢如下:

 <select id="selectUserAndRoleById" resultType="User">
      select
        u.*,
        r.id "role.id",
        r.role "role.role",
        r.enabled "role.enabled"
        from user u
        inner join role r on u.id = r.user_id
        where u.id = #{id}
    </select>

我們在User實體中對Role進行映射。如下:
role映射
當我們查詢一對一的關聯數據時,就可以自動映射到role實體上去。MyBatis還支持複雜的屬性映射 , 可以多層嵌套。 例如,將 role.role 映射到 role.role上。 MyBatis 會先查找role屬性,如果存在role屬性就創建 role對象, 然後在role對象中繼續查找role,將role的值綁定到role對象的role屬性上。

2、使用resultMap配置一對一映射

在上面我們直接通過在實體中進行了映射,除此之外,我們還可以在resultMap中進行配置,對多個不同的屬性進行配置。

    <resultMap id="userMap" type="net.anumbrella.mybatis.entity.User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="email" column="email"/>
        <result property="expried" column="expried"/>
        <result property="disabled" column="disabled"/>
        <result property="role.id" column="role.id"/>
        <result property="role.role" column="role.role"/>
        <result property="role.enabled" column="role.enabled"/>
    </resultMap>

同時因爲MyBatis是支持resultMap映射繼承的, 因此要先簡化上面的resultMap配置。我們可以把角色抽出來單獨作爲一個resultMap,然後繼承(在resultMap中使用extends標籤繼承)用戶屬性的resultMap即可。由於這個內容比較簡單,這裏就不過多敘述。

3、使用resultMap的association標籤配置一對一映射

在resultMap中,assocation標籤用於和一個複雜的類型進行關聯, 即用於一對一的關聯配置。

assocation標籤包含以下屬性:

  • property:對應實體類中的屬性名,必填項。
  • javaType:屬性對應的Java類型。
  • resultMap:可以直接使用現有的resultMap,而不需要在這裏配置。
  • columPrefix:查詢列的前綴,配置前綴後,在子標籤result的colum時可以省略前綴。

因此上面的resultMap可以更改如下:

 <resultMap id="userRoleMap" extends="userMap" type="net.anumbrella.mybatis.entity.User">
        <association property="role" columnPrefix="role_" javaType="net.anumbrella.mybatis.entity.Role">
            <result property="role.id" column="role.id"/>
            <result property="role.role" column="role.role"/>
            <result property="role.enabled" column="role.enabled"/>
        </association>
    </resultMap>

注意:這裏是帶表前綴的,所以在查出的結果對應列中需要加入role_前綴。

同時我們還可以進一步精簡,如下:

    <resultMap id="roleMap" type="net.anumbrella.mybatis.entity.Role">
        <id property="id" column="id"/>
        <result property="role" column="role"/>
        <result property="enabled" column="enabled"/>
    </resultMap>


	<resultMap id="userRoleMap" extends="userMap" type="net.anumbrella.mybatis.entity.User">
        <association property="role" columnPrefix="role_" resultMap="net.anumbrella.mybatis.dao.RoleDao.roleMap"/>
    </resultMap>
4、association標籤的嵌套查詢

association標籤的嵌套查詢常用的屬性如下:

  • select:另一個映射查詢的id, MyBatis會額外執行這個查詢獲取嵌套對象的結果。
  • column:列名(或別名),將主查詢中列的結果作爲嵌套查詢的參數,配置方式如column={propl=coll,prop2=col2}, propl和prop2將作爲嵌套查詢的參數。
  • fetchType:數據加載方式,可選值爲lazy和eager ,分別爲延遲加載和積極加載,這個配置會覆蓋全局的 lazyLoadingEnabled配置。

比如下面的查詢結果配置,同時我們也在select中配置了子查詢。

 <resultMap id="userRoleMapSelect" extends="userMap" type="net.anumbrella.mybatis.entity.User">
        <association property="role"
                     fetchType="lazy"
                     select="net.anumbrella.mybatis.dao.RoleDao.selectRoleById"
                     column="{id=role.id}"/>
    </resultMap>

子查詢在RoleDao.xml配置,如下:

    <select id="selectRoleById" resultMap="roleMap">
      select * from role where id = #{id}
    </select>

因爲第一個SQL的查詢結果只有一條,role.id關聯了另一個查詢,因此執行了兩次SQL。

如果查詢的不是1條數據,而是N條數據,那就會出現N+1問題,主SQL會查詢一次,查詢出N條結果, 這N條結果要各自執行一次查詢,那就需要進行N次查詢。 如何解決這個問題呢?

在上面介紹association標籤的屬性時, 介紹了fetchType數據加載方式, 這個方式可以幫我們實現延遲加載,解決N+1的問題。

在 MyBatis 的全局配置中, 有一個參數爲 aggressiveLazyLoading 。這個參數的含義是,當該參數設置爲 true時,對任意延遲屬性的調用會使帶有延遲加載屬性的對象完整加載,反之, 每種屬性都將按需加載。

因此當我們使用MyBatis懶加載時,需要配置aggressiveLazyLoading爲false。如下:

<configuration>
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
</configuration>

按照上面的介紹,需要把fetchType設置爲lazy(這裏設置lazy,相當於把lazyLoadingEnabled設置爲true),這樣設置後,只有當調用getRole()方法獲取role的時候, MyBatis纔會執行嵌套查詢去獲取數據。

但是有些時候還是需要在觸發某方法時將所有的數據都加載進來 , 而我們己經將aggressiveLazyLoading設置爲 false ,這種情況又該怎麼解決呢?

MyBatis 仍然提供了參數 lazyLoadTriggerMethods 幫助解決這個問題, 這個參數的含義是, 當調用配置中的方法時, 加載全部的延遲加載數據。 默認值爲” equals , clone ,hashCode ,toString ” 。

二、一對多映射

1、collection集合的嵌套結果映射

在前文我們介紹一對一的映射關係中,有多種實現方式,但是一對多的實現方式只有一種方式————collection。

和association類似集合的嵌套結果映射就是指通過一次SQL查詢將所有的結果查詢出來, 然後通過配置的結果映射, 將數據映射到不同的對象中去。

比如,我們在User實體中添加roleList對象,同時添加set/get方法。

public class User implements Serializable{

    private Long id;

    private String username;

    private String password;

    private Integer expired;

    private Integer disabled;

    private String email;

    private Role role;

    private List<Role> roleList;

   // set/get方法
    ...
}

然後我們添加關聯查詢,如下:

   <resultMap id="userRoleListMap" extends="userMap" type="net.anumbrella.mybatis.entity.User">
        <collection property="roleList" columnPrefix="role_"
                    resultMap="net.anumbrella.mybatis.dao.RoleDao.roleMap"/>
   </resultMap>
    
    
   <select id="selectAllUserAndRoles" resultMap="userRoleListMap">
      select
        u.*,
        r.id "role_id",
        r.role "role_role",
        r.enabled "role_enabled"
        from user u
        inner join role r on u.id = r.user_id
    </select>

接着在UserDao中添加selectAllUserAndRoles方法。

SQL執行的結果數有 3 條, 後面輸出的用戶數是 2 , 本來查詢出的3條結果經過MyBatis對collection數據的處理後,變成了兩條。

collection

因爲這裏使用了collection,它會把集合的數據合併。那麼MyBatis的合併規則如何呢?

MyBatis 判斷結果是否相同時, 最簡單的情況就是在映射配置中 至少有一個id標籤 , 在userMap中配置如下。

<id property =” id ” column=” id ” />

我們對 id (構造方法中爲 idArg )的理解一般是 , 它配置的字段爲表的主鍵(聯合主鍵時可以配置多個 id 標籤), 因爲MyBatis的 resultMap 只用於配置結果如何映射 , 並不知道這個表具體如何。 id 的唯一作用就是 在嵌套的映射配置時判斷數據是否相同, 當配置 id 標籤時, MyBatis只需要逐條比較所有數據中 id 標籤配置的字段值是否相同即可。 在配置嵌套結果查詢時 , 配置 id 標籤可以提高處理效率。

因爲前兩條數據的userMap部分的 id 相同 , 所以它們屬於同一個用戶, 因此這條數據會合併到同一個用戶中。

雖然 association 和 collection 標籤是分開介紹的 , 但是這兩者可以組合使用或者互相嵌套使用 , 也可以使用 符合自己需要的任何數據結 構, 不需要侷限於數據庫表之間的關聯關係 。

2、collection集合的嵌套查詢

集合的嵌套查詢也association類似,這裏不過多講述,這是大致提示一下。

同樣的我們添加相關查詢,如下:

    <resultMap id="userRoleListMapSelect" extends="userMap" type="net.anumbrella.mybatis.entity.User">
        <collection property="roleList"
                    fetchType="lazy"
                    select="net.anumbrella.mybatis.dao.RoleDao.selectRoleByUserId"
                    column="{userId=id}"/>
    </resultMap>
    
    <select id="selectAllUserAndRolesSelect" resultMap="userRoleListMapSelect">
        select
        u.id,
        u.username,
        u.password,
        u.email,
        u.expired,
        u.disabled
        from user u
        where u.id = #{id}
    </select>

同理,我們在RoleDao.xml中配置selectRoleByUserId的查詢,如下:

<select id="selectRoleByUserId" resultMap="roleMap">
    select * from role where user_id = #{userId}
</select>

同理因爲所有嵌套查詢都 配置爲延遲加載 , 因此不存在 N+1 的問題 。

三、鑑別器映射

有時一個單獨的數據庫查詢會返回很多不同數據類型(希望有些關聯)的結果集。

鑑別器標籤就是用來處理這種情況的。

discriminator標籤常用的兩個屬性:

  • column:該屬性用於設置要進行鑑別比較值的列。
  • javaType:該屬性用於指定列的類型, 保證使用相同的Java類型來比較值。

discriminator標籤可以有1個或多個case標籤,case標籤包含以下三個屬性:

  • value:該值爲discriminator指定column用來匹配的值。
  • resultMap:當column的值和value的值匹配時,可以配置使用resultMap指定的映射, resultMap優先級高於resultType。
  • resultType:當column的值和value的值匹配時,用於配置使用resultType指定的映射。
<resultMap id="rolePrivilegeListMapChoose" type="net.anumbrella.mybatis.entity.Role">
    <discriminator column="enabled" javaType="int">
        <case value="1" resultMap="rolePrivilegeListMapSelect"/>
        <case value="0" resultMap="roleMap"/>
    </discriminator>
</resultMap>

比如,像上面的enabled爲不同的值時會根據情況返回不同的resultMap作爲結果。

在discriminator中也可以使用javaType返回屬性。

<resultMap id="rolePrivilegeListMapChoose" type="net.anumbrella.mybatis.entity.Role">
    <discriminator column="enabled" javaType="int">
        <case value="1" resultMap="rolePrivilegeListMapSelect"/>
        <case value="0" resultMap="roleMap"/>
        <case value="0"  resultType="net.anumbrella.mybatis.entity.Role>
         <id property ="id" column=“id” /> 
         <result property ="role" column="role" />
        </case>
    </discriminator>
</resultMap>

鑑別器是一種很少使用的方式 , 在使用前一定要完全掌握, 沒有把握 的情況下要儘可能避免使用。

關於MyBatis的高級結果映射的介紹到此爲止,更多詳細的用法可以去查閱相關文檔。

上面的代碼示例:mybatis-demo

參考

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