深入淺出MyBatis:「對映器」全瞭解

情情說發表於2018-03-27

本篇文章是「深入淺出MyBatis:技術原理與實踐」書籍的總結筆記。

上一篇總結了MyBatis的配置,詳細說明了各個配置項,其中提到了對映器,它是MyBatis最強大的工具,也是使用最多的工具。

通過對映器,可以很容易的進行資料的增刪改查操作,我們抽象下進行這些操作的關鍵點:傳遞查詢引數、組裝各種場景下的查詢條件、關聯查詢、將查詢結果對映為Java Bean物件或集合等。另外,可以通過延遲載入、快取提高資料查詢的效能。

文章索引:

  1. JDBC和MyBatis介紹
  2. MyBatis的所有配置
  3. 「對映器」全瞭解
  4. 反射和動態代理基礎

本篇就按照這個思路進行總結,首先列舉下對映器的主要元素,每個元素提供的配置項和作用,然後重點介紹引數、結果對映、延遲載入、快取、動態SQL等功能。

對映器的主要元素

對映器是由Java介面和XML檔案(或註解)共同組成的,Java介面主要定義呼叫者介面,XML檔案是配置對映器的核心檔案,包括以下元素:

  • select 查詢語句,可以自定義引數,返回結果集;
  • insert 插入語句,返回一個整數,表示插入的條數;
  • update 更新語句,返回一個整數,表示更新的條數;
  • delete 刪除語句,返回一個整數,表示刪除的條數;
  • sql 允許定義一部分SQL,然後再各個地方引用;
  • resultMap 用來描述從資料庫結果集中來載入物件,還可以配置關聯關係;
  • cache 給定名稱空間的快取配置;

增、刪、改、查操作

查詢

執行select語句前,需要定義引數,執行後,也提供了強大的對映規則或自動對映,將返回的結果集繫結到java bean中。

select元素有很多配置項,下面簡單說明下:

  • paramterType:傳入的引數型別,可以是基本型別、map、自定義的java bean;
  • resultType:返回的結果型別,可以是基本型別、自定義的java bean;
  • resultMap:它是最複雜的元素,可以配置對映規則、級聯、typeHandler等,與ResultType不能同時存在;
  • flushCache:在呼叫SQL後,是否要求清空之前查詢的本地快取和二級快取,主要用於更新快取,預設為false;
  • useCache:啟動二級快取的開關,預設只會啟動一級快取;
  • timeout:設定超時引數,等超時的時候將丟擲異常,單位為秒;
  • fetchSize:獲取記錄的總條數設定;

比如根據米聊號獲取使用者資訊:

<select id="findByMiliao" parameterType="string" resultType="User">
		select
		u.*
		from mxt_user u
		where u.miliao=#{miliao}
</select>
複製程式碼

上一篇介紹配置時,有個設定項autoMappingBehavior,預設為自動對映沒有定義巢狀結果集對映的結果集;還有設定項mapUnderscoreToCamelCase,設定為true時,會自動將以「下劃線」命名的資料庫欄位名,自動對映為以「駝峰式」命名的POJO。

傳遞多個引數時,有3種方式:

  • 使用Map引數;
  • 使用註解方式傳遞;
  • 使用java bean;

使用註解方式如下:

public List<Role> findRoleByNameAndNote(@Param("roleName") String rolename,
@Param("note") String note);
複製程式碼

使用Map傳遞引數,會導致業務可讀性喪失,導致以後擴充套件和維護不方便,不建議;如果引數個數<=5,建議使用註解的方式,因為過多引數將給呼叫者帶來困難;如果引數個數>5,建議使用JavaBean方式;

使用resultMap對映結果集,後面會單獨介紹。

insert

屬性和select大部分都相同, 說下3個不同的屬性:

  • keyProperty:指定哪個列是主鍵,如果是聯合主鍵可以用逗號隔開;
  • keyColumn:指定第幾列是主鍵,不能和keyProperty共用;
  • useGeneratedKeys:是否使用自動增長,預設為false;

當useGeneratedKeys設為true時,在插入的時候,會回填Java Bean的id值,通過返回的物件可獲取主鍵值。

如果想根據一些特殊關係設定主鍵的值,可以在insert標籤內使用selectKey標籤,比如:如果t_role沒有記錄,則需要設定為1,否則取最大id加2:

<insert id="insertRole" useGeneratedKeys="true" keyProperty="id" >
    <selectKey keyProperty="id" resultType="int" order="before">
        select if(max(id) is null,1,max(id)+2) as newId from t_role
    </selectKey> 
</insert>
複製程式碼
update和delete

比較簡單,就不過多介紹了。

引數

上面已經介紹了引數傳遞,另外可以指定引數的型別去讓對應的typeHandler處理它們。

#{age , javaType=int , jdbcType=NUMERIC }
複製程式碼

還可以對一些數值型的引數設定其儲存的精度

#{price, javaType=double , jdbcType=NUMERIC , numericScale=2 }
複製程式碼

一般都是傳遞字串,設定的引數#{name}大部分情況下,會建立預編譯語句,但有時候傳遞的是SQL語句本身,不是需要的引數,可以通過$符號表示,比如傳遞引數columns為"col1,col2,col3",可以寫成下面語句:

select ${columns} from t_tablename
複製程式碼

但要注意sql的安全性,防止sql注入。

sql元素

定義:

<sql id="role_columns">
    id,role_name,note
</sql>
複製程式碼

使用:

<include refid="role_columns">
    <property name="prefix" value="r" />
</include>
複製程式碼

結果對映

元素介紹

resultMap是MyBatis裡面最複雜的元素,它的作用是定義對映規則、級聯的更新、定製型別轉換器等。

由以下元素構成:

<resultMap>
    <constructor> <!-- 配置構造方法 -->
        <idArg/>
        <arg/>
    </constructor>
    <id/> <!--指明哪一列是主鍵-->
    <result/> <!--配置對映規則-->
    <association/> <!--一對一-->
    <collection/> <!--一對多-->
    <discriminator> <!--鑑別器級聯-->
        <case/>
    </discriminator>
</resultMap>
複製程式碼

有的實體不存在沒有引數的構造方法,需要使用constructor配置有引數的構造方法:

<resultMap id="role" type="com.xiaomi.kfs.mcc.core.domain.Role">
    <constructor>
        <idArg column="id" javaType="int"/>
        <arg column="role_name" javaType="string"/>
    </constructor>
</resultMap>
複製程式碼

id指明主鍵列,result配置資料庫欄位和POJO屬性的對映規則:

<resultMap id="role" type="com.xiaomi.kfs.mcc.core.domain.Role">
    <id property="id" column="id" />
    <result property="roleName" column="role_name" />
    <result property="note" column="note" />
</resultMap>
複製程式碼

association、collection用於配置級聯關係的,分別為一對一和一對多,實際中,多對多關係的應用不多,因為比較複雜,會用一對多的關係把它分解為雙向關係。

discriminator用於這樣一種場景:比如我們去體檢,男和女的體檢專案不同,如果讓男生去檢查婦科專案,是不合理的, 通過discriminator可以根據性別,返回不同的物件。

級聯關係的配置比較多,就不在此演示了,可檢視文件進行了解。

延遲載入

級聯的優勢是能夠方便地獲取資料,但有時不需要獲取所有資料,這樣會多執行幾條SQL,效能下降,為了解決這個問題,需要使用延遲載入,只要使用相關級聯資料時,才會傳送SQL去取回資料。

在MyBatis的配置中有2個全域性的引數 lazyLoadingEnabled 和 aggressiveLazyLoading ,第一個的含義是是否開啟延遲載入功能,第二個的含義是對任意延遲載入屬性的呼叫,會使延遲載入的物件完整載入,否則只會按需載入。

再理解下aggressiveLazyLoading屬性,比如學生物件的關聯物件如下:

深入淺出MyBatis:「對映器」全瞭解

當訪問學生資訊的時候,會根據鑑別器把健康的情況也會查詢出來;當訪問課程成績的時候,同時也會把學生證資訊查詢出來,因為在預設情況下,MyBatis是按層級延遲載入的。 但這不是我們需要的,並不希望在訪問成績的時候,去載入學生證的資訊,可以設定aggressiveLazyLoading為false,按需進行延遲載入資料。

上面的2個屬性都是全域性設定,也可以在association和collection元素上加上屬性值fetchType,它有兩個取值eager和lazy。

快取

在沒有顯示配置快取時,只開啟一級快取,一級快取是相對於同一個SqlSession而言的,在引數和SQL完全一樣的情況下,使用同一個SqlSession物件呼叫同一個Mapper的方法,只會執行一次SQL。

如果是不同的SqlSession物件,因為不同SqlSession是相互隔離的,即使用相同的Mapper、引數和方法,還是會再次傳送SQL到資料庫去執行。

二級快取是SqlSessionFactory層面上的,需要進行顯示配置,實現二級快取的時候,要求POJO必須是可序列化的,只需要簡單配置即可:

<cache />
複製程式碼

這樣很多設定是預設的,有如下屬性可以配置:

  • eviction:代表快取回收策略,可選值有LRU最少使用、FIFO先進先出、SOFT軟引用,WEAK弱引用;
  • flushInterval:重新整理間隔時間,單位為毫秒,如果不配置,當SQL被執行時才會重新整理快取;
  • size:引用數目,代表快取最多可以儲存多少物件,不宜設定過大,設定過大會導致記憶體溢位;
  • readOnly:只讀,意味著快取資料只能讀取不能修改;

在大型伺服器上,可能會使用專用的快取伺服器,比如Redis快取,可以通過實現org.apache.ibatis.cache.Cache介面很方便的實現:

public interface Cache {
    String getId(); //快取編號
    void putObject(Object var1, Object var2); //儲存物件
    Object getObject(Object var1); //獲取物件
    Object removeObject(Object var1); //移除物件
    void clear(); //清空快取
    int getSize(); //獲取快取物件大小
    ReadWriteLock getReadWriteLock(); //獲取快取的讀寫鎖
}
複製程式碼

動態SQL

很多時候,需要根據不同的場景組裝查詢條件,MyBatis提供對SQL語句動態的組裝能力。

主要提供以下幾種元素:

  • if:判斷語句,但條件分支判斷;
  • choose (when、otherwise):多條件分支判斷;
  • trim (where、set):處理一些SQL拼裝問題;
  • foreach:迴圈語句,在in語句等列舉條件常用;
  • bind:通過OGNL表示式去自定義一個上下文變數,可以方便使用;

trim可以處理 and 和 逗號 拼接的問題,舉例如下:

<select id="findRoles" parameterType="string" >
  select id,role_name,note from t_role
  <trim prefix="where" prefixOverrides="and">
      <if test="roleName!=null and roleName!=''">
        and role_name like concat('%',#{roleName},'%')
      </if>
  </trim>
</select>
複製程式碼

另外,可以使用set元素設定更新的欄位列表:

<update id="updateRole" parameterType="role">
    update t_role
    <set>
        <if test="roleName!=null and roleName!=''">
            role_name=#{roleName},
        </if>
        <if test="note!=null and note!=''">
            note=#{note}
        </if>
    </set>
    where id=#{id}
</update>
複製程式碼

下一篇會介紹MyBatis的解析和執行原理。

歡迎掃描下方二維碼,關注我的個人微信公眾號 ~

情情說

相關文章