Mapper映射文件配置

一、概述

在mapper文件中蛮寂,已mapper作為根節(jié)點,其下面可以配置的元素節(jié)點有:select,insert,update,delete,cache,cache-ref,resultMap,sql

二、詳細配置解析

1. insert,update缤骨,delete的配置及使用

<?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 為根元素節(jié)點, 一個namespace對應(yīng)一個dao -->
<!-- 
Mapper元素只有一個屬性namespace尺借,它有兩個作用:`一是用于區(qū)分不同的mapper`(在不同的mapper文件里绊起,子元素的id可以相同,mybatis通過namespace和子元素的id聯(lián)合區(qū)分)燎斩,`二是與接口關(guān)聯(lián)`(應(yīng)用程序通過接口訪問mybatis時虱歪,mybatis通過接口的完整名稱查找對應(yīng)的mapper配置,因此namespace的命名務(wù)必小心一定要某接口同名)栅表。
-->
<mapper namespace="com.dy.dao.UserDao">
    
    <!-- 
    cache- 配置本定命名空間的緩存笋鄙。
        type- cache實現(xiàn)類,默認為PERPETUAL怪瓶,可以使用自定義的cache實現(xiàn)類(別名或完整類名皆可)
        eviction- 回收算法萧落,默認為LRU,可選的算法有:
            LRU– 最近最少使用的:移除最長時間不被使用的對象劳殖。
            FIFO– 先進先出:按對象進入緩存的順序來移除它們铐尚。
            SOFT– 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象拨脉。
            WEAK– 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象哆姻。
        flushInterval- 刷新間隔,默認為1個小時玫膀,單位毫秒
        size- 緩存大小矛缨,默認大小1024帖旨,單位為引用數(shù)
        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為準(zhǔn)。
    -->
    <cache-ref namespace="com.someone.application.data.SomeMapper"/>
    
    <insert
      <!-- 1. id (必須配置)
        id是命名空間中的唯一標(biāo)識符蟹地,可被用來代表這條語句积暖。 
        一個命名空間(namespace) 對應(yīng)一個dao接口, 
        這個id也應(yīng)該對應(yīng)dao里面的某個方法(相當(dāng)于方法的實現(xiàn)),因此id 應(yīng)該與方法名一致 -->
      
      id="insertUser"
      
      <!-- 2. parameterType (可選配置, 默認為mybatis自動選擇處理)
        將要傳入語句的參數(shù)的完全限定類名或別名怪与, 如果不配置夺刑,mybatis會通過ParameterHandler 根據(jù)參數(shù)類型默認選擇合適的typeHandler進行處理
        parameterType 主要指定參數(shù)類型,可以是int, short, long, string等類型,也可以是復(fù)雜類型(如對象) -->
      
      parameterType="com.demo.User"
      
      <!-- 3. flushCache (可選配置遍愿,默認配置為true)
        將其設(shè)置為 true存淫,任何時候只要語句被調(diào)用,都會導(dǎo)致本地緩存和二級緩存都會被清空沼填,默認值:true(對應(yīng)插入纫雁、更新和刪除語句) -->
      
      flushCache="true"
      
      <!-- 4. statementType (可選配置,默認配置為PREPARED)
        STATEMENT倾哺,PREPARED 或 CALLABLE 的一個轧邪。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement羞海,默認值:PREPARED忌愚。 -->
      
      statementType="PREPARED"
      
      <!-- 5. keyProperty (可選配置, 默認為unset)
        (僅對 insert 和 update 有用)唯一標(biāo)記一個屬性却邓,MyBatis 會通過 getGeneratedKeys 的返回值或者通過 insert 語句的 selectKey 子元素設(shè)置它的鍵值硕糊,默認:unset。如果希望得到多個生成的列腊徙,也可以是逗號分隔的屬性名稱列表简十。 -->
      
      keyProperty=""
      
      <!-- 6. keyColumn     (可選配置)
        (僅對 insert 和 update 有用)通過生成的鍵值設(shè)置表中的列名,這個設(shè)置僅在某些數(shù)據(jù)庫(像 PostgreSQL)是必須的撬腾,當(dāng)主鍵列不是表中的第一列的時候需要設(shè)置螟蝙。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表民傻。 -->
      
      keyColumn=""
      
      <!-- 7. useGeneratedKeys (可選配置胰默, 默認為false)
        (僅對 insert 和 update 有用)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數(shù)據(jù)庫內(nèi)部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關(guān)系數(shù)據(jù)庫管理系統(tǒng)的自動遞增字段),默認值:false漓踢。  -->
      
      useGeneratedKeys="false"
      
      <!-- 8. timeout  (可選配置牵署, 默認為unset, 依賴驅(qū)動)
        這個設(shè)置是在拋出異常之前,驅(qū)動程序等待數(shù)據(jù)庫返回請求結(jié)果的秒數(shù)喧半。默認值為 unset(依賴驅(qū)動)奴迅。 -->
      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>

在oracle數(shù)據(jù)庫中不支持id自增長,就需要selectKey的配置:

 <!-- 對應(yīng)userDao中的insertUser方法挺据,  -->
   <insert id="insertUser" parameterType="com.dy.entity.User">
        <!-- oracle等不支持id自增長的取具,可根據(jù)其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中吴菠,如果需要數(shù)據(jù)插入后返回插入的id者填,也可以使用selectKey元素:

<!-- 對應(yīng)userDao中的insertUser方法,  -->
   <insert id="insertUser" parameterType="com.dy.entity.User">
        <!-- oracle等不支持id自增長的做葵,可根據(jù)其id生成策略占哟,先獲取id 
        <selectKey resultType="int" order="BEFORE" keyProperty="id">
              select seq_user_id.nextval as id from dual
        </selectKey>
        --> 
        
        <!-- mysql插入數(shù)據(jù)后,獲取id,該方法LAST_INSERT_ID()與數(shù)據(jù)庫連接綁定榨乎,同屬統(tǒng)一會話級別怎燥。-->
        <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提供了一個簡單的行為,在數(shù)據(jù)庫中處理自動生成的組件蜜暑,而不需要在Java代碼中進行業(yè)務(wù)處理铐姚。在上面的例子中,selectKey元素將會首先運行肛捍,userid會被設(shè)置隐绵,然后插入語句會被調(diào)用。另外selectKey節(jié)點生成的KeyGenerator優(yōu)先級高舉statement節(jié)點的userGeneratedKeys屬性生成的KeyGenerator對象拙毫,也就是說配置了selectKey子節(jié)點就不需要再配置userGeneratedKeys屬性了依许。

<selectKey
        <!-- selectKey 語句結(jié)果應(yīng)該被設(shè)置的目標(biāo)屬性。如果希望得到多個生成的列缀蹄,也可以是逗號分隔的屬性名稱列表峭跳。 -->
        keyProperty="id"
        <!-- 結(jié)果的類型。MyBatis 通橙鼻埃可以推算出來蛀醉,但是為了更加確定寫上也不會有什么問題。MyBatis 允許任何簡單類型用作主鍵的類型衅码,包括字符串拯刁。如果希望作用于多個生成的列,則可以使用一個包含期望屬性的 Object 或一個 Map肆良。 -->
        resultType="int"
        <!-- 這可以被設(shè)置為 BEFORE 或 AFTER筛璧。如果設(shè)置為 BEFORE逸绎,那么它會首先選擇主鍵惹恃,設(shè)置 keyProperty 然后執(zhí)行插入語句。如果設(shè)置為 AFTER棺牧,那么先執(zhí)行插入語句巫糙,然后是 selectKey 元素 - 這和像 Oracle 的數(shù)據(jù)庫相似,在插入語句內(nèi)部可能有嵌入索引調(diào)用颊乘。 -->
        order="BEFORE"
        <!-- 與前面相同参淹,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 語句的映射類型乏悄,分別代表 PreparedStatement 和 CallableStatement 類型浙值。 -->
        statementType="PREPARED">

2. select、resultMap的配置及使用
select是最常用的檩小,也是最復(fù)雜的开呐,mybatis通過resultMao能很好地進行高級映射。

<select
     <!--  1. id (必須配置)
        id是命名空間中的唯一標(biāo)識符,可被用來代表這條語句筐付。 
        一個命名空間(namespace) 對應(yīng)一個dao接口, 
        這個id也應(yīng)該對應(yīng)dao里面的某個方法(相當(dāng)于方法的實現(xiàn))卵惦,因此id 應(yīng)該與方法名一致
     -->
     
     id="selectPerson"
     
     <!-- 2. parameterType (可選配置, 默認為mybatis自動選擇處理)
        將要傳入語句的參數(shù)的完全限定類名或別名, 如果不配置瓦戚,mybatis會通過ParameterHandler 根據(jù)參數(shù)類型默認選擇合適的typeHandler進行處理
        parameterType 主要指定參數(shù)類型沮尿,可以是int, short, long, string等類型,也可以是復(fù)雜類型(如對象) -->
     parameterType="int"
     
     <!-- 3. resultType (resultType 與 resultMap 二選一配置)
         resultType用以指定返回類型较解,指定的類型可以是基本類型舷礼,可以是java容器,也可以是javabean -->
     resultType="hashmap"
     
     <!-- 4. resultMap (resultType 與 resultMap 二選一配置)
         resultMap用于引用我們通過 resultMap標(biāo)簽定義的映射類型审胸,這也是mybatis組件高級復(fù)雜映射的關(guān)鍵 -->
     resultMap="personResultMap"
     
     <!-- 5. flushCache (可選配置)
         將其設(shè)置為 true轿腺,任何時候只要語句被調(diào)用,都會導(dǎo)致本地緩存和二級緩存都會被清空当编,默認值:false -->
     flushCache="false"
     
     <!-- 6. useCache (可選配置)
         將其設(shè)置為 true届慈,將會導(dǎo)致本條語句的結(jié)果被二級緩存,默認值:對 select 元素為 true -->
     useCache="true"
     
     <!-- 7. timeout (可選配置) 
         這個設(shè)置是在拋出異常之前忿偷,驅(qū)動程序等待數(shù)據(jù)庫返回請求結(jié)果的秒數(shù)金顿。默認值為 unset(依賴驅(qū)動)-->
     timeout="10000"
     
     <!-- 8. fetchSize (可選配置) 
         這是嘗試影響驅(qū)動程序每次批量返回的結(jié)果行數(shù)和這個設(shè)置值相等。默認值為 unset(依賴驅(qū)動)-->
     fetchSize="256"
     
     <!-- 9. statementType (可選配置) 
         STATEMENT鲤桥,PREPARED 或 CALLABLE 的一個揍拆。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement茶凳,默認值:PREPARED-->
     statementType="PREPARED"
     
     <!-- 10. resultSetType (可選配置) 
         FORWARD_ONLY嫂拴,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一個,默認值為 unset (依賴驅(qū)動)-->
     resultSetType="FORWARD_ONLY">

在MyBatis中處理一對多贮喧,多對多筒狠,一對一的關(guān)系時就需要用到resultMap了,MyBatis的resultMap功能十分強大箱沦,能夠處理復(fù)雜的關(guān)系映射辩恼。

 <!-- 
        resultMap –結(jié)果映射,用來描述如何從數(shù)據(jù)庫結(jié)果集映射到你想要的對象谓形。

        1.type 對應(yīng)類型灶伊,可以是javabean, 也可以是其它
        2.id 必須唯一, 用于標(biāo)示這個resultMap的唯一性寒跳,在使用resultMap的時候聘萨,就是通過id指定
     -->
    <resultMap type="" id="">
    
        <!-- id, 唯一性,注意啦童太,這個id用于標(biāo)示這個javabean對象的唯一性米辐, 不一定會是數(shù)據(jù)庫的主鍵(不要把它理解為數(shù)據(jù)庫對應(yīng)表的主鍵) 
            property屬性對應(yīng)javabean的屬性名碾牌,column對應(yīng)數(shù)據(jù)庫表的列名
            (這樣,當(dāng)javabean的屬性與數(shù)據(jù)庫對應(yīng)表的列名不一致的時候儡循,就能通過指定這個保持正常映射了)
        -->
        <id property="" column=""/>
        
        <!-- result與id相比舶吗, 對應(yīng)普通屬性 -->    
        <result property="" column=""/>
        
        <!-- 
            constructor對應(yīng)javabean中的構(gòu)造方法
         -->
        <constructor>
            <!-- idArg 對應(yīng)構(gòu)造方法中的id參數(shù);-->
            <idArg column=""/>
            <!-- arg 對應(yīng)構(gòu)造方法中的普通參數(shù)择膝;-->
            <arg column=""/>
        </constructor>
        
        <!-- 
            聚集元素用來處理“一對多”的關(guān)系誓琼。需要指定映射的Java實體類的屬性,屬性的javaType(一般為ArrayList)肴捉;列表中對象的類型ofType(Java實體類)腹侣;對應(yīng)的數(shù)據(jù)庫表的列名稱;

            collection齿穗,對應(yīng)javabean中容器類型, 是實現(xiàn)一對多的關(guān)鍵 
            property 為javabean中容器對應(yīng)字段名
            column 為體現(xiàn)在數(shù)據(jù)庫中列名
            ofType 就是指定javabean中容器指定的類型

            不同情況需要告訴MyBatis 如何加載一個聚集傲隶。MyBatis 可以用兩種方式加載:
                1. select: 執(zhí)行一個其它映射的SQL 語句返回一個Java實體類型。較靈活窃页;
                2. resultMap: 使用一個嵌套的結(jié)果映射來處理通過join查詢結(jié)果集跺株,映射成Java實體類型。
        -->
        <collection property="" column="" ofType=""></collection>
        
        <!-- 
            聯(lián)合元素用來處理“一對一”的關(guān)系脖卖。需要指定映射的Java實體類的屬性乒省,屬性的javaType(通常MyBatis 自己會識別)。對應(yīng)的數(shù)據(jù)庫表的列名稱畦木。如果想覆寫的話返回結(jié)果的值袖扛,需要指定typeHandler。

            association 為關(guān)聯(lián)關(guān)系十籍,是實現(xiàn)N對一的關(guān)鍵蛆封。
            property 為javabean中容器對應(yīng)字段名
            column 為體現(xiàn)在數(shù)據(jù)庫中列名
            javaType 指定關(guān)聯(lián)的類型

            不同情況需要告訴MyBatis 如何加載一個聯(lián)合。MyBatis可以用兩種方式加載:
                1. select: 執(zhí)行一個其它映射的SQL 語句返回一個Java實體類型勾栗。較靈活惨篱;
                2. resultMap: 使用一個嵌套的結(jié)果映射來處理,通過join查詢結(jié)果集械姻,映射成Java實體類型妒蛇。
         -->
        <association property="" column="" javaType=""></association>

        <!-- 
            有時一個單獨的數(shù)據(jù)庫查詢也許返回很多不同(但是希望有些關(guān)聯(lián))數(shù)據(jù)類型的結(jié)果集。鑒別器元素就是被設(shè)計來處理這個情況的楷拳,還有包括類的繼承層次結(jié)構(gòu)。鑒別器非常容易理解吏奸,因為它的表現(xiàn)很像Java語言中的switch語句欢揖。

            定義鑒別器指定了column和javaType屬性。列是MyBatis查找比較值的地方奋蔚。JavaType是需要被用來保證等價測試的合適類型(盡管字符串在很多情形下都會有用)她混。

            下面這個例子為烈钞,當(dāng)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>

3.字符串代入法
使用#{}語法促使MyBatis生存PreparedStatement屬性并且使用PreparedStatement的參數(shù)(=?)來安全的設(shè)置值毯欣。盡量這些是快捷安全,也是經(jīng)常使用的臭脓。但有時候可能想直接未更改的字符串代入到SQL 語句中酗钞。比如說,對于ORDER BY来累,你可能會這樣使用:ORDER BY ${columnName}但MyBatis 不會修改和規(guī)避掉這個字符串砚作。這樣地接收和應(yīng)用一個用戶輸入到未更改的語句中,是非常不安全的嘹锁。這會讓用戶能植入破壞代碼葫录,所以,要么要求字段不要允許客戶輸入领猾,要么你直接來檢測他的合法米同。

4.子元素之cache解析
Mapper配置文件是由XMLMapperBuilder解析的,其中cacheElement方法負責(zé)解析cache元素摔竿,它通過調(diào)用CacheBuilder的相應(yīng)方法完成cache的創(chuàng)建窍霞。每個cache內(nèi)部都有一個唯一的ID,這個id的值就是namespace拯坟。創(chuàng)建好的cache對象存入configuaration的cache緩存中但金。

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解析
acheRefElement方法負責(zé)解析cache-ref元素,它通過調(diào)用CacheRefResolver的相應(yīng)方法完成cache的引用郁季。創(chuàng)建好的cache-ref引用關(guān)系存入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方法負責(zé)解析resultMap元素,它通過調(diào)用ResultMapResolver的相應(yīng)方法完成resultMap的解析梦裂。創(chuàng)建好的resultMap存入configuration的resultMaps緩存中(該緩存以namespace+resultMap的id為key似枕,這里再次體現(xiàn)了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方法負責(zé)解析sql元素年柠。id屬性用于區(qū)分不同的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方法負責(zé)解析statement元素冗恨。id屬性用于區(qū)分不同的statement元素答憔,在同一個配置文件中可以配置多個statement元素。通過調(diào)用XMLStatementBuilder的parseStatementNode方法完成解析掀抹。在這個方法內(nèi)有幾個重要的步驟虐拓,理解他們對正確的配置statement元素很有幫助。
8.1 動態(tài)解析子元素
statement節(jié)點可以配置各種子元素傲武,比如前面提到的include子元素和selectKey子元素等(在動態(tài)sql里還有更多的子元素蓉驹,具體參考mybatis的官方文檔)城榛。動態(tài)解析子元素通過parseDynamicTags方法完成。該方法根據(jù)子元素的類型遞歸的解析成一個個的SqlNode态兴,這些SqlNode對象提供了apply方法狠持,供后續(xù)調(diào)用時生成sql語句所需。需要注意的是SelectKey沒有對應(yīng)的SqlNode對象瞻润,因為它的功能是用來生成KeyGenerator對象的(具體來說是SelectKeyGenerator對象)喘垂。另外,SelectKey節(jié)點生成的KeyGenerator優(yōu)先級高于statement節(jié)點的useGeneratedKeys屬性生成的KeyGenerator對象敢订,也就是說配置了SelectKey子節(jié)點就不需要再配置useGeneratedKeys屬性了王污。
8.2 生成sqlSource
SqlSource用于后續(xù)調(diào)用時根據(jù)SqlNode和參數(shù)對象生成sql語句。它接收一個叫做rootSqlNode的對象作為構(gòu)造參數(shù)楚午。
8.3 生成KeyGenerator
如果配置了selectKey子元素昭齐,KeyGenerator直接使用selectKey子元素里生成的KeyGenerator對象(具體來說是SelectKeyGenerator對象)。若沒配置矾柜,則如果useGeneratedKeys屬性的值為"true"且配置了 keyProperty屬性阱驾,則生成默認的Jdbc3KeyGenerator對象,該對象調(diào)用JDBC驅(qū)動的getGeneratedKeys方法返回insert語句執(zhí)行后生成的自增長主鍵怪蔑。
8.4 創(chuàng)建MappedStatement
MappedStatement對象封裝了statement元素的所有屬性以及子節(jié)點值里覆,MappedStatement對象有一個id屬性用于唯一標(biāo)記它,這個id由namespace加statement元素的id屬性值構(gòu)成缆瓣。創(chuàng)建好的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:

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屬性對應(yīng)于某個接口弓坞,引用程序通過接口訪問MyBatis時隧甚,Mybatis會為這個接口生存一個代理對象,這個對象就叫mapper對象渡冻,在生成代理對象錢MyBatis會校驗接口是否已注冊戚扳,未注冊的接口會產(chǎn)生一個異常。為了避免這種異常族吻,就需要注冊mapper類型帽借,這個步驟實在XMLMapperBuilder的bindMapperForNamespace方法中完成的。它通過調(diào)用Configuaration對象的addMapper方法完成超歌,而Configuration對象的addMapper方法是通過MapperRegistry的addMapper方法完成的砍艾,它只是簡單的將namespace屬性對象的接口類型存入本地緩存中。
Configuration對象提供了一個重載的addMappers(StringpackageName)方法握础,該方法以包路徑名為參數(shù)辐董,它的功能是自動掃描包路徑下的接口并注冊到MapperRegistry的緩存中,同時掃描包路徑下的mapper配置文件并解析之禀综。解析配置文件是在MapperAnnotationBuilder類的parse方法里完成的简烘,該方法先解析配置文件,然后再解析接口里的注解配置定枷,且注解里的配置會覆蓋配置文件里的配置孤澎,也就是說注解的優(yōu)先級高于配置文件,這點需要注意欠窒。采用自動掃描會大大簡化配置覆旭,只不過需要應(yīng)用程序自己調(diào)用,MyBatis默認是不會調(diào)用這個方法的岖妄。

 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);
        }
      }
    }
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末型将,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子荐虐,更是在濱河造成了極大的恐慌七兜,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件福扬,死亡現(xiàn)場離奇詭異腕铸,居然都是意外死亡,警方通過查閱死者的電腦和手機铛碑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門狠裹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汽烦,你說我怎么就攤上這事涛菠。” “怎么了撇吞?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵俗冻,是天一觀的道長。 經(jīng)常有香客問我梢夯,道長言疗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任颂砸,我火速辦了婚禮噪奄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘人乓。我一直安慰自己勤篮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布色罚。 她就那樣靜靜地躺著碰缔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪戳护。 梳的紋絲不亂的頭發(fā)上金抡,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天瀑焦,我揣著相機與錄音,去河邊找鬼梗肝。 笑死榛瓮,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的巫击。 我是一名探鬼主播禀晓,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坝锰!你這毒婦竟也來了粹懒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤顷级,失蹤者是張志新(化名)和其女友劉穎凫乖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愕把,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡拣凹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了恨豁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嚣镜。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖橘蜜,靈堂內(nèi)的尸體忽然破棺而出菊匿,到底是詐尸還是另有隱情,我是刑警寧澤计福,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布跌捆,位于F島的核電站,受9級特大地震影響象颖,放射性物質(zhì)發(fā)生泄漏佩厚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一说订、第九天 我趴在偏房一處隱蔽的房頂上張望抄瓦。 院中可真熱鬧,春花似錦陶冷、人聲如沸钙姊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煞额。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間膊毁,已是汗流浹背胀莹。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留媚媒,地道東北人嗜逻。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓涩僻,卻偏偏與公主長得像缭召,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子逆日,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容