【深刻淺出MyBatis系列三】Mapper映射文件配置

#0 系列目錄#java

在mapper文件中,以mapper做爲根節點,其下面能夠配置的元素節點有: select, insert, update, delete, cache, cache-ref, resultMap, sql 。node

#1 insert, update, delete 的配置及使用# 相信,看到insert, update, delete, 咱們就知道其做用了,顧名思義嘛,myabtis 做爲持久層框架,必需要對CRUD啊。好啦,我們就先來看看 insert, update, delete 怎麼配置, 能配置哪些元素吧:mysql

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

<!-- mapper 爲根元素節點, 一個namespace對應一個dao -->
<!-- 
Mapper元素只有一個屬性namespace,它有兩個做用:`一是用於區分不一樣的mapper`(在不一樣的mapper文件裏,子元素的id能夠相同,mybatis經過namespace和子元素的id聯合區分),`二是與接口關聯`(應用程序經過接口訪問mybatis時,mybatis經過接口的完整名稱查找對應的mapper配置,所以namespace的命名務必當心必定要某接口同名)。
-->
<mapper namespace="com.dy.dao.UserDao">
    
    <!-- 
    cache- 配置本定命名空間的緩存。
        type- cache實現類,默認爲PERPETUAL,可使用自定義的cache實現類(別名或完整類名皆可)
        eviction- 回收算法,默認爲LRU,可選的算法有:
            LRU– 最近最少使用的:移除最長時間不被使用的對象。
            FIFO– 先進先出:按對象進入緩存的順序來移除它們。
            SOFT– 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。
            WEAK– 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象。
        flushInterval- 刷新間隔,默認爲1個小時,單位毫秒
        size- 緩存大小,默認大小1024,單位爲引用數
        readOnly- 只讀
    -->
    <cache type="PERPETUAL" eviction="LRU" flushInterval="60000"  
        size="512" readOnly="true" />
    
    <!-- 
    cache-ref–從其餘命名空間引用緩存配置。
        若是你不想定義本身的cache,可使用cache-ref引用別的cache。由於每一個cache都以namespace爲id,因此cache-ref只須要配置一個namespace屬性就能夠了。須要注意的是,若是cache-ref和cache都配置了,以cache爲準。
    -->
    <cache-ref namespace="com.someone.application.data.SomeMapper"/>
    
    <insert
      <!-- 1. id (必須配置)
        id是命名空間中的惟一標識符,可被用來表明這條語句。 
        一個命名空間(namespace) 對應一個dao接口, 
        這個id也應該對應dao裏面的某個方法(至關於方法的實現),所以id 應該與方法名一致 -->
      
      id="insertUser"
      
      <!-- 2. parameterType (可選配置, 默認爲mybatis自動選擇處理)
        將要傳入語句的參數的徹底限定類名或別名, 若是不配置,mybatis會經過ParameterHandler 根據參數類型默認選擇合適的typeHandler進行處理
        parameterType 主要指定參數類型,能夠是int, short, long, string等類型,也能夠是複雜類型(如對象) -->
      
      parameterType="com.demo.User"
      
      <!-- 3. flushCache (可選配置,默認配置爲true)
        將其設置爲 true,任什麼時候候只要語句被調用,都會致使本地緩存和二級緩存都會被清空,默認值:true(對應插入、更新和刪除語句) -->
      
      flushCache="true"
      
      <!-- 4. statementType (可選配置,默認配置爲PREPARED)
        STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認值:PREPARED。 -->
      
      statementType="PREPARED"
      
      <!-- 5. keyProperty (可選配置, 默認爲unset)
        (僅對 insert 和 update 有用)惟一標記一個屬性,MyBatis 會經過 getGeneratedKeys 的返回值或者經過 insert 語句的 selectKey 子元素設置它的鍵值,默認:unset。若是但願獲得多個生成的列,也能夠是逗號分隔的屬性名稱列表。 -->
      
      keyProperty=""
      
      <!-- 6. keyColumn     (可選配置)
        (僅對 insert 和 update 有用)經過生成的鍵值設置表中的列名,這個設置僅在某些數據庫(像 PostgreSQL)是必須的,當主鍵列不是表中的第一列的時候須要設置。若是但願獲得多個生成的列,也能夠是逗號分隔的屬性名稱列表。 -->
      
      keyColumn=""
      
      <!-- 7. useGeneratedKeys (可選配置, 默認爲false)
        (僅對 insert 和 update 有用)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數據庫內部生成的主鍵(好比:像 MySQL 和 SQL Server 這樣的關係數據庫管理系統的自動遞增字段),默認值:false。  -->
      
      useGeneratedKeys="false"
      
      <!-- 8. timeout  (可選配置, 默認爲unset, 依賴驅動)
        這個設置是在拋出異常以前,驅動程序等待數據庫返回請求結果的秒數。默認值爲 unset(依賴驅動)。 -->
      timeout="20">

    <update
      id="updateUser"
      parameterType="com.demo.User"
      flushCache="true"
      statementType="PREPARED"
      timeout="20">

    <delete
      id="deleteUser"
      parameterType="com.demo.User"
      flushCache="true"
      statementType="PREPARED"
      timeout="20">
</mapper>

以上就是一個模板配置, 哪些是必要配置,哪些是根據本身實際需求,看一眼就知道了。看一個真實的UserDao-Mapper.xml配置:算法

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

<mapper namespace="com.dy.dao.UserDao">
   
   <!-- 對應userDao中的insertUser方法,  -->
   <insert id="insertUser" parameterType="com.dy.entity.User">
           insert into user(id, name, password, age, deleteFlag) 
               values(#{id}, #{name}, #{password}, #{age}, #{deleteFlag})
   </insert>
   
   <!-- 對應userDao中的updateUser方法 -->
   <update id="updateUser" parameterType="com.dy.entity.User">
           update user set name = #{name}, password = #{password}, age = #{age}, deleteFlag = #{deleteFlag}
               where id = #{id};
   </update>
    
   <!-- 對應userDao中的deleteUser 方法 --> 
   <delete id="deleteUser" parameterType="com.dy.entity.User">
           delete from user where id = #{id};
   </delete>
</mapper>

這樣,一個簡單的映射關係就創建了。仔細觀察上面parameterType, "com.dy.entity.User",包名要是再長點呢,每次都這樣寫,寫得蛋疼了。別忘了以前講的 typeAliases(別名), 那麼這個地方,用上別名,豈不是技能跟蛋疼的長長的包名說拜拜了。好啦,我們配上別名,在哪兒配? 固然是在mybatis 的全局配置文件(我這兒名字是mybatis-conf.xml), 不要認爲是在mapper的配置文件裏面配置哈。spring

<typeAliases>
     <!--
        經過package, 能夠直接指定package的名字, mybatis會自動掃描你指定包下面的javabean,
        而且默認設置一個別名,默認的名字爲: javabean 的首字母小寫的非限定類名來做爲它的別名。
        也可在javabean 加上註解@Alias 來自定義別名, 例如: @Alias(user) 
        <package name="com.dy.entity"/>
     -->
     <typeAlias alias="user" type="com.dy.entity.User"/>
</typeAliases>

這樣,一個別名就取好了,我們能夠把上面的 com.dy.entity.User 都直接改成user 了。 這多方便呀!sql

我這兒數據庫用的是mysql, 我把user表的主鍵id 設置了自動增加, 以上代碼運行正常, 那麼問題來了(固然,我不是要問學挖掘機哪家強),我要是換成oracle數據庫怎麼辦? oracle 但是不支持id自增加啊? 怎麼辦?請看下面:數據庫

<!-- 對應userDao中的insertUser方法,  -->
   <insert id="insertUser" parameterType="com.dy.entity.User">
        <!-- oracle等不支持id自增加的,可根據其id生成策略,先獲取id -->  
        <selectKey resultType="int" order="BEFORE" keyProperty="id">
              select seq_user_id.nextval as id from dual
        </selectKey>
        
           
        insert into user(id, name, password, age, deleteFlag) 
        values(#{id}, #{name}, #{password}, #{age}, #{deleteFlag})
   </insert>

同理,若是咱們在使用mysql的時候,想在數據插入後返回插入的id, 咱們也可使用 selectKey 這個元素apache

<!-- 對應userDao中的insertUser方法,  -->
   <insert id="insertUser" parameterType="com.dy.entity.User">
        <!-- oracle等不支持id自增加的,可根據其id生成策略,先獲取id 
        <selectKey resultType="int" order="BEFORE" keyProperty="id">
              select seq_user_id.nextval as id from dual
        </selectKey>
        --> 
        
        <!-- mysql插入數據後,獲取id,該方法LAST_INSERT_ID()與數據庫鏈接綁定,同屬統一會話級別。-->
        <selectKey keyProperty="id" resultType="int" order="AFTER" >
               SELECT LAST_INSERT_ID() as id
        </selectKey>
          
        insert into user(id, name, password, age, deleteFlag) 
        values(#{id}, #{name}, #{password}, #{age}, #{deleteFlag})
   </insert>

**這兒,咱們就簡單提一下 <selectKey> 這個元素節點吧:**selectKey給了你一個簡單的行爲在你的數據庫中來處理自動生成的主鍵,而不須要使你的Java代碼變得複雜。在上面的示例中,selectKey元素將會首先運行,userid會被設置,而後插入語句會被調用。另外,selectKey節點生成的KeyGenerator優先級高於statement節點的useGeneratedKeys屬性生成的KeyGenerator對象,也就是說配置了SelectKey子節點就不須要再配置useGeneratedKeys屬性了緩存

<selectKey
        <!-- selectKey 語句結果應該被設置的目標屬性。若是但願獲得多個生成的列,也能夠是逗號分隔的屬性名稱列表。 -->
        keyProperty="id"
        <!-- 結果的類型。MyBatis 一般能夠推算出來,可是爲了更加肯定寫上也不會有什麼問題。MyBatis 容許任何簡單類型用做主鍵的類型,包括字符串。若是但願做用於多個生成的列,則可使用一個包含指望屬性的 Object 或一個 Map。 -->
        resultType="int"
        <!-- 這能夠被設置爲 BEFORE 或 AFTER。若是設置爲 BEFORE,那麼它會首先選擇主鍵,設置 keyProperty 而後執行插入語句。若是設置爲 AFTER,那麼先執行插入語句,而後是 selectKey 元素 - 這和像 Oracle 的數據庫類似,在插入語句內部可能有嵌入索引調用。 -->
        order="BEFORE"
        <!-- 與前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 語句的映射類型,分別表明 PreparedStatement 和 CallableStatement 類型。 -->
        statementType="PREPARED">

#2 select、resultMap的配置及使用## select無疑是咱們最經常使用,也是最複雜的,mybatis經過resultMap能幫助咱們很好地進行高級映射。下面就開始看看select 以及 resultMap的用法:安全

先看select的配置吧:

<select
     <!--  1. id (必須配置)
        id是命名空間中的惟一標識符,可被用來表明這條語句。 
        一個命名空間(namespace) 對應一個dao接口, 
        這個id也應該對應dao裏面的某個方法(至關於方法的實現),所以id 應該與方法名一致
     -->
     
     id="selectPerson"
     
     <!-- 2. parameterType (可選配置, 默認爲mybatis自動選擇處理)
        將要傳入語句的參數的徹底限定類名或別名, 若是不配置,mybatis會經過ParameterHandler 根據參數類型默認選擇合適的typeHandler進行處理
        parameterType 主要指定參數類型,能夠是int, short, long, string等類型,也能夠是複雜類型(如對象) -->
     parameterType="int"
     
     <!-- 3. resultType (resultType 與 resultMap 二選一配置)
         resultType用以指定返回類型,指定的類型能夠是基本類型,能夠是java容器,也能夠是javabean -->
     resultType="hashmap"
     
     <!-- 4. resultMap (resultType 與 resultMap 二選一配置)
         resultMap用於引用咱們經過 resultMap標籤訂義的映射類型,這也是mybatis組件高級複雜映射的關鍵 -->
     resultMap="personResultMap"
     
     <!-- 5. flushCache (可選配置)
         將其設置爲 true,任什麼時候候只要語句被調用,都會致使本地緩存和二級緩存都會被清空,默認值:false -->
     flushCache="false"
     
     <!-- 6. useCache (可選配置)
         將其設置爲 true,將會致使本條語句的結果被二級緩存,默認值:對 select 元素爲 true -->
     useCache="true"
     
     <!-- 7. timeout (可選配置) 
         這個設置是在拋出異常以前,驅動程序等待數據庫返回請求結果的秒數。默認值爲 unset(依賴驅動)-->
     timeout="10000"
     
     <!-- 8. fetchSize (可選配置) 
         這是嘗試影響驅動程序每次批量返回的結果行數和這個設置值相等。默認值爲 unset(依賴驅動)-->
     fetchSize="256"
     
     <!-- 9. statementType (可選配置) 
         STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認值:PREPARED-->
     statementType="PREPARED"
     
     <!-- 10. resultSetType (可選配置) 
         FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一個,默認值爲 unset (依賴驅動)-->
     resultSetType="FORWARD_ONLY">

配置看起來老是這麼多,不過實際經常使用的配置也就那麼幾個, 根據本身的須要吧,上面都已註明是否必須配置。看一個CourseDao-Mapper.xml配置:

<mapper namespace="com.dy.dao.CourseDao">
    
    <!-- 
         1.此處直接將resultType 設置爲course, 一看就知道我設置了別名吧,若是沒有設置別名,那麼resultType = com.dy.entity.Course。
         2.可能細心的你會發現:Course.java中的屬性名與數據庫字段名不一致,下面,我就在sql語句中用了as, 使之匹配,固然方法不止一種,在學習了resultMap以後,你能看到一種更直觀優雅的方式去將javabean中的屬性與數據庫字段名保持一致
         3.findCourseById 與CourseDao中findCourseById方法對應, 那麼傳入的參數名稱以及類型也應該保持對應關係。
         4.能夠看到,在sql語句中,經過#{}表達式能夠獲取參數。
         5.下面這條sql語句,實際上的形式是怎麼樣的?還記得以前說過,mybatis默認爲preparedStatement吧,那麼,用咱們jdbc代碼來看,它其實就是:
             select course_id as id, course_name as name, course_delete_flg as deleteFlag from t_course where course_id=?
     -->
    <select id="findCourseById"  resultType="course" >
        select course_id as id, course_name as name, course_delete_flg as deleteFlag from t_course where course_id=#{courseId}
    </select>
</mapper>

上面的示例,咱們針對course, 簡單演示了 select的用法, 不過有個問題值得思考: 一個student能夠對應多個course, 那麼,在mybatis中如何處理這種一對多, 甚至於多對多,一對一的關係呢?

這兒,就不得不提到 resultMap 這個東西, mybatis的resultMap功能可謂十分強大,可以處理複雜的關係映射, 那麼resultMap 該怎麼配置呢? 別急,這就來了:

<!-- 
        resultMap –結果映射,用來描述如何從數據庫結果集映射到你想要的對象。

        1.type 對應類型,能夠是javabean, 也能夠是其它
        2.id 必須惟一, 用於標示這個resultMap的惟一性,在使用resultMap的時候,就是經過id指定
     -->
    <resultMap type="" id="">
    
        <!-- id, 惟一性,注意啦,這個id用於標示這個javabean對象的惟一性, 不必定會是數據庫的主鍵(不要把它理解爲數據庫對應表的主鍵) 
            property屬性對應javabean的屬性名,column對應數據庫表的列名
            (這樣,當javabean的屬性與數據庫對應表的列名不一致的時候,就能經過指定這個保持正常映射了)
        -->
        <id property="" column=""/>
        
        <!-- result與id相比, 對應普通屬性 -->    
        <result property="" column=""/>
        
        <!-- 
            constructor對應javabean中的構造方法
         -->
        <constructor>
            <!-- idArg 對應構造方法中的id參數;-->
            <idArg column=""/>
            <!-- arg 對應構造方法中的普通參數;-->
            <arg column=""/>
        </constructor>
        
        <!-- 
            彙集元素用來處理「一對多」的關係。須要指定映射的Java實體類的屬性,屬性的javaType(通常爲ArrayList);列表中對象的類型ofType(Java實體類);對應的數據庫表的列名稱;

            collection,對應javabean中容器類型, 是實現一對多的關鍵 
            property 爲javabean中容器對應字段名
            column 爲體如今數據庫中列名
            ofType 就是指定javabean中容器指定的類型

            不一樣狀況須要告訴MyBatis 如何加載一個彙集。MyBatis 能夠用兩種方式加載:
                1. select: 執行一個其它映射的SQL 語句返回一個Java實體類型。較靈活;
                2. resultMap: 使用一個嵌套的結果映射來處理經過join查詢結果集,映射成Java實體類型。
        -->
        <collection property="" column="" ofType=""></collection>
        
        <!-- 
            聯合元素用來處理「一對一」的關係。須要指定映射的Java實體類的屬性,屬性的javaType(一般MyBatis 本身會識別)。對應的數據庫表的列名稱。若是想覆寫的話返回結果的值,須要指定typeHandler。

            association 爲關聯關係,是實現N對一的關鍵。
            property 爲javabean中容器對應字段名
            column 爲體如今數據庫中列名
            javaType 指定關聯的類型

            不一樣狀況須要告訴MyBatis 如何加載一個聯合。MyBatis能夠用兩種方式加載:
                1. select: 執行一個其它映射的SQL 語句返回一個Java實體類型。較靈活;
                2. resultMap: 使用一個嵌套的結果映射來處理,經過join查詢結果集,映射成Java實體類型。
         -->
        <association property="" column="" javaType=""></association>

        <!-- 
            有時一個單獨的數據庫查詢也許返回不少不一樣(可是但願有些關聯)數據類型的結果集。鑑別器元素就是被設計來處理這個狀況的,還有包括類的繼承層次結構。鑑別器很是容易理解,由於它的表現很像Java語言中的switch語句。

            定義鑑別器指定了column和javaType屬性。列是MyBatis查找比較值的地方。JavaType是須要被用來保證等價測試的合適類型(儘管字符串在不少情形下都會有用)。

            下面這個例子爲,當classId爲20000001時,才映射classId屬性。
        -->
        <discriminator column="CLASS_ID" javaType="String" jdbcType="VARCHAR">  
            <case value="20000001" resultType="liming.student.manager.data.model.StudentEntity" >  
                <result property="classId" column="CLASS_ID" javaType="String" jdbcType="VARCHAR"/>  
            </case> 
        </discriminator>
    </resultMap>

好啦,知道resutMap怎麼配置後,我們當即接着上面的demo來練習一下吧,一個student對應多個course, 典型的一對多,我們就來看看mybatis怎麼配置這種映射吧:StudentDao-Mapper.xml

<mapper namespace="com.dy.dao.StudentDao">

    <!-- 這兒定義一個resultMap -->
    <resultMap type="student" id="studentMap">
    
        <!-- 
            數據庫中主鍵是id, 可是我這兒倒是指定idCard爲主鍵,爲何? 
            剛剛講了,id用來表示惟一性, 咱們能夠認爲只要idCard同樣,那麼他就是同一個學生。
            若是此處用數據庫中id, 那麼mybatis將會認爲數據庫中每條記錄都是一個student, 這顯然不符合邏輯
        -->
        <id property="idCard" column="stu_id_card"/>
        <result property="id" column="stu_id"/>
        <result property="name" column="stu_name"/>
        <result property="deleteFlag" column="stu_delete_flg"/>
  
        <constructor>  
            <idArg javaType="String" column="STUDENT_ID"/>  
            <arg javaType="String" column="STUDENT_NAME"/>  
            <arg javaType="String" column="STUDENT_SEX"/>  
            <arg javaType="Date" column="STUDENT_BIRTHDAY"/>  
        </constructor>
        
        <!-- 
            這兒就是實現一對多的關鍵。 
            在Student中,courseList爲List<Course>, 所以,ofType也應該與之對應(固然,我用了別名,否則要蛋疼的寫全名了)。
            collection的子標籤是在指定Course的映射關係(因爲Course的javabean的屬性名與數據庫的列名不一致)
        -->
        <collection property="courseList" column="stu_course_id" ofType="Course">
            <id property="id" column="course_id"/>
            <result property="name" column="course_name"/>
            <result property="deleteFlag" column="course_delete_flg"/>
        </collection>
    </resultMap>
    
    <!-- 這兒將返回類型設置成了上面指定的studentMap -->
    <select id="findStudentById" resultMap="studentMap">
        SELECT s.*, c.* FROM t_student s LEFT JOIN t_course c ON s.stu_course_id=c.course_id WHERE s.stu_id_card=#{idCard}
    </select>

    <!-- 
        sql –能夠重用的SQL塊,能夠被其餘數據庫操做語句引用。
    -->
    <sql id="userColumns"> userid,username,password</sql>
    
    <select id="queryUsers" parameterType="UserDto" resultType="UserDto" useCache="false">
	select <include refid="userColumns"/> from t_user t where t.username = #{username}
    </select>
    
</mapper>

固然,咱們須要定義StudentEntity實體類的構造方法:

public StudentEntity(String studentID, String studentName, String studentSex, Date studentBirthday){  
    this.studentID = studentID;  
    this.studentName = studentName;  
    this.studentSex = studentSex;  
    this.studentBirthday = studentBirthday;  
}

相信經過以上示例, 你們也可以使用mybatis的select 和 resultMap的用法了。上面只演示了一對多的映射,其實多對1、多對多也與它相似,因此我就沒演示了,有興趣的能夠本身動手再作作。

#3 字符串代入法# 默認的狀況下,使用#{}語法會促使MyBatis 生成PreparedStatement 屬性而且使用PreparedStatement 的參數(=?)來安全的設置值。儘可能這些是快捷安全,也是常用的。但有時候你可能想直接未更改的字符串代入到SQL 語句中。好比說,對於ORDER BY,你可能會這樣使用:ORDER BY ${columnName}但MyBatis 不會修改和規避掉這個字符串

注意:這樣地接收和應用一個用戶輸入到未更改的語句中,是很是不安全的。這會讓用戶能植入破壞代碼,因此,要麼要求字段不要容許客戶輸入,要麼你直接來檢測他的合法性 。

#4 子元素之cache解析# Mapper配置文件是由XMLMapperBuilder解析的,其中cacheElement方法負責解析cache元素,它經過調用CacheBuilder的相應方法完成cache的建立。每一個cache內部都有一個惟一的ID,這個id的值就是namespace。建立好的cache對象存入configuration的cache緩存中(該緩存以cache的ID屬性即namespace爲key,這裏再次體現了mybatis的namespace的強大用處)。

private void cacheElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      Properties props = context.getChildrenAsProperties();
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);
    }
  }

#5 子元素之cache-ref解析# cacheRefElement方法負責解析cache-ref元素,它經過調用CacheRefResolver的相應方法完成cache的引用。建立好的cache-ref引用關係存入configuration的cacheRefMap緩存中。

private void cacheRefElement(XNode context) {
    if (context != null) {
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
    	  cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
    	  configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }

#6 子元素之resultMap解析# resultMapElement方法負責解析resultMap元素,它經過調用ResultMapResolver的相應方法完成resultMap的解析。建立好的resultMap存入configuration的resultMaps緩存中(該緩存以namespace+resultMap的id爲key,這裏再次體現了mybatis的namespace的強大用處)。

private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
  }

  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

#7 子元素之sql解析# sqlElement方法負責解析sql元素。id屬性用於區分不一樣的sql元素,在同一個mapper配置文件中能夠配置多個sql元素。

private void sqlElement(List<XNode> list) throws Exception {
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
  }

  private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      id = builderAssistant.applyCurrentNamespace(id, false);
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context);
    }
  }
  
  private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
      if (!requiredDatabaseId.equals(databaseId)) {
        return false;
      }
    } else {
      if (databaseId != null) {
        return false;
      }
      // skip this fragment if there is a previous one with a not null databaseId
      if (this.sqlFragments.containsKey(id)) {
        XNode context = this.sqlFragments.get(id);
        if (context.getStringAttribute("databaseId") != null) {
          return false;
        }
      }
    }
    return true;
  }

#8 子元素之statement解析# buildStatementFromContext方法負責解析statement元素。id屬性用於區分不一樣的statement元素,在同一個配置文件中能夠配置多個statement元素。經過調用XMLStatementBuilder的parseStatementNode方法完成解析。在這個方法內有幾個重要的步驟,理解他們對正確的配置statement元素頗有幫助。

  1. 動態解析子元素

statement節點能夠配置各類子元素,好比前面提到的include子元素和selectKey子元素等(在動態sql裏還有更多的子元素,具體參考mybatis的官方文檔)。動態解析子元素經過parseDynamicTags方法完成。該方法根據子元素的類型遞歸的解析成一個個的SqlNode,這些SqlNode對象提供了apply方法,供後續調用時生成sql語句所需。須要注意的是SelectKey沒有對應的SqlNode對象,由於它的功能是用來生成KeyGenerator對象的(具體來講是SelectKeyGenerator對象)。另外,SelectKey節點生成的KeyGenerator優先級高於statement節點的useGeneratedKeys屬性生成的KeyGenerator對象,也就是說配置了SelectKey子節點就不須要再配置useGeneratedKeys屬性了。

  1. 生成SqlSource

SqlSource用於後續調用時根據SqlNode和參數對象生成sql語句。它接收一個叫作rootsqlNode的對象做爲構造參數。

  1. 生成KeyGenerator

若是配置了selectKey子元素,KeyGenerator直接使用selectKey子元素裏生成的KeyGenerator對象(具體來講是SelectKeyGenerator對象)。若沒配置,則若是useGeneratedKeys屬性的值爲"true"且配置了 keyProperty屬性,則生成默認的Jdbc3KeyGenerator對象,該對象調用JDBC驅動的getGeneratedKeys方法返回insert語句執行後生成的自增加主鍵。

  1. 建立MappedStatement

MappedStatement對象封裝了statement元素的全部屬性以及子節點值,MappedStatement對象有一個id屬性用於惟一標記它,這個id由namespace加statement元素的id屬性值構成。建立好的MappedStatement對象存入Configuration對象的mappedStatements緩存中,key爲MappedStatement對象的id值。

XMLMapperBuilder.java:

private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

XMLStatementBuilder.java:

public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

#9 註冊mapper類型# 咱們知道每一個mapper配置文件的namespace屬性對應於某個接口,應用程序經過接口訪問mybatis時,mybatis會爲這個接口生成一個代理對象,這個對象就叫mapper對象,在生成代理對象前mybatis會校驗接口是否已註冊,未註冊的接口會產生一個異常。爲了不這種異常,就須要註冊mapper類型。這個步驟是在XMLMapperBuilder的bindMapperForNamespace方法中完成的。它經過調用Configuration對象的addMapper方法完成,而Configuration對象的addMapper方法是經過MapperRegistry的addMapper方法完成的,它只是簡單的將namespace屬性對應的接口類型存入本地緩存中

Configuration對象提供了一個重載的addMappers(StringpackageName)方法,該方法以包路徑名爲參數,它的功能是自動掃描包路徑下的接口並註冊到MapperRegistry的緩存中,同時掃描包路徑下的mapper配置文件並解析之。解析配置文件是在MapperAnnotationBuilder類的parse方法裏完成的,該方法先解析配置文件,而後再解析接口裏的註解配置,且註解裏的配置會覆蓋配置文件裏的配置,也就是說註解的優先級高於配置文件,這點須要注意。採用自動掃描會大大簡化配置,只不過須要應用程序本身調用,mybatis默認是不會調用這個方法的(後續將會講解的spring集成mybatis就用到了自動掃描)

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }
相關文章
相關標籤/搜索