一、概述
在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);
}
}
}
}