Mybatis初始化流程(二)
1. Properties variables的作用
通常,我們會單獨配置jdbc.properties檔案,保存於variables變數中,而Xml檔案內可以使用${driver}佔位符,讀取時可動態替換佔位符的值。
2. 掃描package
<typeAliases> <typeAlias alias="Student" type="com.mybatis3.domain.Student" /> <typeAlias alias="Teacher" type="com.mybatis3.domain.Teacher" /> <package name="com.mybatis3.domain" /> </typeAliases>
前兩個typeAlias,很容易理解,那麼
public void registerAliases(String packageName, Class<?> superType){ ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); for(Class<?> type : typeSet){ // 排除內部類、介面 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { registerAlias(type); } } }
3. namespace如何對映Mapper介面
org.apache.ibatis.builder.xml.XMLMapperBuilder.bindMapperForNamespace()。
直接使用Class.forName(),成功找到就註冊,找不到就什麼也不做。
Mybatis中的namespace有兩個功能。
-
和其名字含義一樣,作為名稱空間使用。namespace + id,就能找到對應的Sql。
-
作為Mapper介面的全限名使用,通過namespace,就能找到對應的Mapper介面(也有稱Dao介面的)。Mybatis推薦的最佳實踐,但並不強制使用。
Mapper介面註冊至Configuration的MapperRegistry mapperRegistry內。
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>()
Mapper介面將通過MapperProxyFactory建立動態代理物件。
4. 一個MappedStatement被快取了兩個引用的原理及原因
configuration.addMappedStatement(statement);
呼叫上面一句話,往Map裡放置一個MappedStatement物件,結果Map中變成兩個元素。
com.mybatis3.mappers.StudentMapper.findAllStudents=org.apache.ibatis.mapping.MappedStatement@add0edd
findAllStudents=org.apache.ibatis.mapping.MappedStatement@add0edd
我們的問題是,為什麼會變成兩個元素?同一個物件,為什麼要存有兩個鍵的引用?
其實,在Mybatis中,這些Map,都是StrictMap型別,Mybatis在StrictMap內做了手腳。
protected static class StrictMap<V> extends HashMap<String, V> {
public V put(String key, V value) {
if (containsKey(key)) {
throw new IllegalArgumentException(name + " already contains value for " + key);
}
if (key.contains(".")) {
final String shortKey = getShortName(key);
// 不存在shortKey鍵值,放進去
if (super.get(shortKey) == null) {
super.put(shortKey, value);
} else {
// 存在shortKey鍵值,填充佔位物件Ambiguity
super.put(shortKey, (V) new Ambiguity(shortKey));
}
}
return super.put(key, value);
}
}
Mybatis重寫了put方法,將id和namespace+id的鍵,都put了進去,指向同一個MappedStatement物件。如果shortKey鍵值存在,就填充為佔位符物件Ambiguity,屬於覆蓋操作
。
這樣做的好處是,方便我們程式設計。
Student std = sqlSession.selectOne("findStudentById", 1);
Student std = sqlSession.selectOne("com.mybatis3.mappers.StudentMapper.findStudentById", 1);
上面兩句程式碼,是等價的,Mybatis不強制我們一定要加namespace名稱空間,所以,這是存放兩個鍵的良苦用心。
問題:不同namespace空間下的id,能否相同呢?(網上的說法是,不同名稱空間下的id可以相同)
明白上述put原理後,就不難得出結論,namespace名稱空間不同,而id相同時,使用namespace+id獲取Sql,完全可以正確執行。如果只用id獲取,那麼,將導致錯誤。
org.apache.ibatis.session.Configuration.StrictMap.get()方法原始碼。
public V get(Object key) {
V value = super.get(key);
if (value == null) {
throw new IllegalArgumentException(name + " does not contain value for " + key);
}
if (value instanceof Ambiguity) {
throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
+ " (try using the full name including the namespace, or rename one of the entries)");
}
return value;
}
get時,如果得到的是一個佔位物件Ambiguity,就丟擲異常,要求使用full name進行呼叫。full name就是namespace+id。Ambiguity意為模糊不清。
解決辦法:
-
保證shortKey(即id)不重複。(好像有點難度,不推薦)
-
使用繫結Mapper介面呼叫方法,因為它總是轉換為full name呼叫。(Mybatis最佳實踐,推薦)
-
直接使用字串full name呼叫。(退而求其次的方式,不推薦)
5. 初始化過程中的mapped和incomplete物件
翻譯為搞定的和還沒搞定的。這恐怕是Mybatis框架中比較奇葩的設計了,給人很多迷惑,我們來看看它具體是什麼意思。
<resultMap type="Student" id="StudentResult" extends="Parent">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="dob" column="dob" />
</resultMap>
<resultMap type="Student" id="Parent">
<result property="phone" column="phone" />
</resultMap>
Mapper.xml中的很多元素,是可以指定父元素的,像上面extends="Parent"。然而,Mybatis解析元素時,是按順序解析的,於是先解析的id="StudentResult"的元素,然而該元素繼承自id="Parent"的元素,但是,Parent被配置在下面了,還沒有解析到,記憶體中尚不存在,怎麼辦呢?Mybatis就把id="StudentResult"的元素標記為incomplete的,然後繼續解析後續元素。等程式把id="Parent"的元素也解析完後,再回過頭來解析id="StudentResult"的元素,就可以正確繼承父元素的內容。
先標記 後解析能解析到的 回頭再解析一邊之前標記過的 這個順序
簡言之就是,你的父元素可以配置在你的後邊,不限制非得配置在前面。無論你配置在哪兒,Mybatis都能“智慧”的獲取到,並正確繼承。
這便是在Configuration物件內,有的叫mapped,有的叫incomplete的原因。
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
org.apache.ibatis.builder.xml.XMLMapperBuilder.parse()方法內,觸發了incomplete的再度解析。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
// 執行incomplete的地方
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
Pending含義為待定的,懸而未決的意思。
總結:有了這些細節儲備,閱讀原始碼就變得更加得心應手了。