1. 程式人生 > 其它 >Mybatis初始化流程(二)

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有兩個功能。

  1. 和其名字含義一樣,作為名稱空間使用。namespace + id,就能找到對應的Sql。

  2. 作為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意為模糊不清。

解決辦法:

  1. 保證shortKey(即id)不重複。(好像有點難度,不推薦)

  2. 使用繫結Mapper介面呼叫方法,因為它總是轉換為full name呼叫。(Mybatis最佳實踐,推薦)

  3. 直接使用字串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含義為待定的,懸而未決的意思。

總結:有了這些細節儲備,閱讀原始碼就變得更加得心應手了。