#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元素頗有幫助。
statement節點能夠配置各類子元素,好比前面提到的include子元素和selectKey子元素等(在動態sql裏還有更多的子元素,具體參考mybatis的官方文檔)。動態解析子元素經過parseDynamicTags方法完成。該方法根據子元素的類型遞歸的解析成一個個的SqlNode,這些SqlNode對象提供了apply方法,供後續調用時生成sql語句所需。須要注意的是SelectKey沒有對應的SqlNode對象,由於它的功能是用來生成KeyGenerator對象的(具體來講是SelectKeyGenerator對象)。另外,SelectKey節點生成的KeyGenerator優先級高於statement節點的useGeneratedKeys屬性生成的KeyGenerator對象,也就是說配置了SelectKey子節點就不須要再配置useGeneratedKeys屬性了。
SqlSource用於後續調用時根據SqlNode和參數對象生成sql語句。它接收一個叫作rootsqlNode的對象做爲構造參數。
若是配置了selectKey子元素,KeyGenerator直接使用selectKey子元素裏生成的KeyGenerator對象(具體來講是SelectKeyGenerator對象)。若沒配置,則若是useGeneratedKeys屬性的值爲"true"且配置了 keyProperty屬性,則生成默認的Jdbc3KeyGenerator對象,該對象調用JDBC驅動的getGeneratedKeys方法返回insert語句執行後生成的自增加主鍵。
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); } } } }