#0 系列目錄#
- 深入淺出MyBatis系列
- 【深入淺出MyBatis系列一】MyBatis入門
- 【深入淺出MyBatis系列二】配置簡介(MyBatis源碼篇)
- 【深入淺出MyBatis系列三】Mapper映射檔案配置
- 【深入淺出MyBatis系列四】強大的動态SQL
- 【深入淺出MyBatis系列五】SQL執行流程分析(源碼篇)
- 【深入淺出MyBatis系列六】插件原理
- 【深入淺出MyBatis系列七】分頁插件
- 【深入淺出MyBatis系列八】SQL自動生成插件
- 【深入淺出MyBatis系列九】改造Cache插件
- 【深入淺出MyBatis系列十】與Spring內建
- 【深入淺出MyBatis系列十一】緩存源碼分析
- 【深入淺出MyBatis系列十二】終結篇:MyBatis原理深入解析
在mapper檔案中,以mapper作為根節點,其下面可以配置的元素節點有: select, insert, update, delete, cache, cache-ref, resultMap, sql 。
#1 insert, update, delete 的配置及使用# 相信,看到insert, update, delete, 我們就知道其作用了,顧名思義嘛,myabtis 作為持久層架構,必須要對CRUD啊。好啦,咱們就先來看看 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 為根元素節點, 一個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的配置檔案裡面配置哈。
<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 了。 這多友善呀!
我這兒資料庫用的是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 這個元素
:
<!-- 對應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的用法了。上面隻示範了一對多的映射,其實多對一、多對多也與它類似,是以我就沒示範了,有興趣的可以自己動手再做做。
#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
SqlSource用于後續調用時根據SqlNode和參數對象生成sql語句。它接收一個叫做rootsqlNode的對象作為構造參數。
- 生成KeyGenerator
如果配置了selectKey子元素,KeyGenerator直接使用selectKey子元素裡生成的KeyGenerator對象(具體來說是SelectKeyGenerator對象)。若沒配置,則如果useGeneratedKeys屬性的值為"true"且配置了 keyProperty屬性,則生成預設的Jdbc3KeyGenerator對象,該對象調用JDBC驅動的getGeneratedKeys方法傳回insert語句執行後生成的自增長主鍵。
- 建立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);
}
}
}
}
版權聲明:本文為CSDN部落客「weixin_34208283」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。
原文連結:https://blog.csdn.net/weixin_34208283/article/details/91531952