Mybatis源碼分析-配置模塊

樓主比較菜,肯定有很多說的不對的地方,主要還是寫給自己看的!!
比起spring來說,mybatis實在是簡單,所以就先來聊聊mybatis!
先來張mybatis整體的結構圖 瞧瞧
Mybatis設計結構如下

從MyBatis代碼實現的角度來看,MyBatis的主要的核心部件有以下幾個:
1 SqlSession 作爲MyBatis工作的主要頂層API,表示和數據庫交互的會話,完成必要數據庫增刪改查功能
2 Executor MyBatis執行器,是MyBatis 調度的核心,負責SQL語句的生成和查詢緩存的維護
3 StatementHandler 封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設置參數、將Statement結果集轉換成List集合。
4 ParameterHandler 負責對用戶傳遞的參數轉換成JDBC Statement 所需要的參數,
5 ResultSetHandler 負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合;
6 TypeHandler 負責java數據類型和jdbc數據類型之間的映射和轉換
7 MappedStatement MappedStatement維護了一條select|update|delete|insert節點的封裝
8 SqlSource 負責根據用戶傳遞的parameterObject,動態地生成SQL語句,將信息封裝到BoundSql對象中,並返回
9 BoundSql 表示動態生成的SQL語句以及相應的參數信息
10 Configuration MyBatis所有的配置信息都維持在Configuration對象之中。

我們用一個列子來看下mybatis 的配置模塊

String resource = "configs/mybatis-config.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
        SqlSession session = sqlSessionFactory.openSession();

1 根據 配置文件或者註解,生成和數據庫交互的必要的數據,存儲於Map中,以供後續使用;
1.1 我們來看下 Reader reader = Resources.getResourceAsReader(resource);
resource 地址我是直接用的resrouces文件夾中的相對地址,作用就是根據地址加載配置文件信息,輸出Reader;

  ClassLoader[] getClassLoaders(ClassLoader classLoader) {
    return new ClassLoader[]{
        classLoader,
        defaultClassLoader,
        Thread.currentThread().getContextClassLoader(),
        getClass().getClassLoader(),
        systemClassLoader};
  }

類加載器爲上述5種,可以看出,以傳入的classLoader和默認的defaultClassLoader爲主,下面三個大家都很熟悉了,那麼一般我們用第三個;這裏用到的兩個爲 org.apache.ibatis.io.Resources(主要用來解析文件,還可以返回Class)和org.apache.ibatis.io.ClassLoaderWrapper(主要是涉及到類加載器的用途,無非是加載文件和加載類),
這裏主要涉及到org.apache.ibatis.io包,看包名就知道 ,該包下面都是關於io的類
這裏寫圖片描述
包中就這麼幾個類,其中vfs類(虛擬文件系統,用來讀取服務器裏的資源),提供了2個實現 JBoss6VFS 和 DefaultVFS,並提供了用戶擴展點,可定義VFS實現;加載順序: 自定義VFS實現 > 默認VFS實現 取第一個加載成功的
1.2 根據Reader解析成mybatis必須數據

    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
 1.2.1 好了,來看下SqlSessionFactoryBuilder 這個類,主要集中在build方法中,該方法提供了很多的重載方法,不一一說,重點說下下面的方法
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

看出,不僅僅可以傳reader,還可以自定義environment(jdbc連接條件)和properties(變量)這兩個配置信息,可以看出 重點又在XMLConfigBuilder這個類中了

 public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }

XPathParser 解析類,主要封裝 通過javax.xml.xpath.XPath來生成Document,具體怎麼解析就不說了,不是本文重點;

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

出現Configuration 這個類,上面講到過 , MyBatis所有的配置信息都維持在Configuration對象之中。從這裏才真正的開始解析配置文件至mybatis中;Configuration 默認構造函數中就註冊了很多別名,其實就是放進map中,這種方式在很多地方都會見到;
1.2.2 parser.parse()
來看看 ,parse方法中發生了什麼;

 public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
重點是parseConfiguration這個方法,可以看出,先從configuration開始解析了;
     private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

很明顯,所有的配置信息全部在改方法中生成,我們挑些講講;
1.2.2.1 propertiesElement(root.evalNode(“properties”));

     Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");

由上可知,屬性變量有三種方式可以加載進來,節點子集、resource和url節點屬性,其中resource和url只能存在一個,最終合併放進配置文件中

 parser.setVariables(defaults);
 configuration.setVariables(defaults);
    1.2.2.2
    typeAliasesElement(root.evalNode("typeAliases"));
    註冊別名,簡單點說就是以別名爲key,class爲value存放於對應的map中,以便後續用
    1.2.2.3 pluginElement(root.evalNode("plugins"));
    註冊插件;先註冊進別名,然後放入插件鏈中
    1.2.2.4 objectFactoryElement(root.evalNode("objectFactory"));
    類創建工程,該類作用僅僅是生成實例,默認是DefaultObjectFactory;我們可以實現我們自定義的工廠,實現ObjectFactory接口即可,可以用於類初始化的作用
    1.2.2.5 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    動態獲取和設置屬性的值,默認使用DefaultObjectWrapperFactory,mybatis基本考慮會很全,自定義的很少使用
    對象包裝工程
    1.2.2.6 reflectorFactoryElement(root.evalNode("reflectorFactory"));
    反射工廠,功能很簡單,就是生成一個反射配置數據,存儲Reflector類(裏面包含了該類涉及到方法、構造函數、字段、類型很全的一套反射信息,賦值、取值都可以通過他來操作。我們以後自己項目也可以直接拿來使用)數據,默認使用DefaultReflectorFactory類
      1.2.2.7 environmentsElement
      主要配置連接執行環境,裏面包含了事務(JdbcTransactionFactory還有ManagedTransactionFactory,一般使用前者)及數據源(POOL、UNPOOL,JNDI之分,一般肯定選擇池)的配置信息的生成;最終生成Environment;Environment這個類比較奇怪,裏面實現了內部類Builder,但是內部類和外部類區別不大 ,何必呢。。
      1.2.2.8   typeHandlerElement(root.evalNode("typeHandlers"));
      類型轉換器,該配置可以根據包名,進而解析整個包獲取,也可以指定轉換類,因爲typeHandlerRegistry類中可以對包進行註冊
      1.2.2.9    mapperElement(root.evalNode("mappers"));
          xml配置文件解析,同.1.2.2.8 可以對包(只能在mapper類同包名下才行),也可以其他方式
     String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");

由上可以看出是三種方式,其中數resource和url複雜,class是很簡單的,
我們這裏舉resource的例子,這裏涉及到XMLMapperBuilder類

            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();

好嘛,mapper.xml解析入口在這裏了。好複雜。。
XMLMapperBuilder(解析mapper.xml配置信息類)和XmlConfigBuilder類似,都繼承BaseBuilder,同樣都有parse 解析方法,只是XMLMapperBuilder更復雜些,因爲mapper.xml中的節點更多更復雜。

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

從configurationElement方法可以看出,xml直接關聯唯一mapper類,那麼可以以此作爲key,進而爲後續調用獲取配置信息打下基礎。
MapperBuilderAssistant用於緩存、sql參數、查詢返回的結果集處理。

SQL 映射文件有很少的幾個頂級元素(按照它們應該被定義的順序):

cache – 給定命名空間的緩存配置。可以配置
映射語句文件中的所有 select 語句將會被緩存。
映射語句文件中的所有 insert,update 和 delete 語句會刷新緩存。
緩存會使用 Least Recently Used(LRU,最近最少使用的)算法來收回。
根據時間表(比如 no Flush Interval,沒有刷新間隔), 緩存不會以任何時間順序 來刷新。
緩存會存儲列表集合或對象(無論查詢方法返回什麼)的 1024 個引用。
緩存會被視爲是 read/write(可讀/可寫)的緩存,意味着對象檢索不是共享的,而 且可以安全地被調用者修改,而不干擾其他調用者或線程所做的潛在修改。
cache-ref – 其他命名空間緩存配置的引用。共用一個namespace緩存
resultMap – 是最複雜也是最強大的元素,用來描述如何從數據庫結果集中來加載對象。
這裏解析的resultMap中的數據,其中涉及到ResultMapping類,主要記錄對應的表及實體類相關配置數據,存進resultMappings 中
parameterMap – 已廢棄!老式風格的參數映射。內聯參數是首選,這個元素可能在將來被移除,這裏不會記錄。
sql – 可被其他語句引用的可重用語句塊。
這塊僅僅是將配置信息存入,而沒有進一步去解析
insert – 映射插入語句
update – 映射更新語句
delete – 映射刪除語句
select – 映射查詢語句
上面四種都是一樣處理,僅僅是類型不一樣而已;
其中涉及到XMLStatementBuilder類,該類主要記錄Statement相關的配置信息MappedStatement

好了 最麻煩的也處理完成了!!返回Configuration,所有的配置信息全在裏面了

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

返回DefaultSqlSessionFactory
接下來是下面這段代碼

SqlSession session = sqlSessionFactory.openSession();
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

開啓會話,來看看到底幹什麼了!
看代碼,初始化了事務Transaction,根據例子,其實這裏真實的對象應該是JdbcTransaction
也初始化了Executor ,mybatis的執行器

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

看代碼,executor 最終被CachingExecutor裝飾了,執行時,先執行CachingExecutor,再執行SimpleExecutor(我們例子裏是simple)

  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }

額,構造函數裏又把CachingExecutor傳遞給SimpleExecutor了,這是要幹啥,沒看到什麼用途,先不管,繼續繼續!
executor = (Executor) interceptorChain.pluginAll(executor);
將執行器放入插件鏈中,判斷是否符合自定義插件類型,符合 則生成代理,則以後凡是到了執行器這裏,則優先進入自定義插件執行!
這就是製作插件的原理,使用代理!
最終返回DefaultSqlSession,又是個Default
好了,會話成功開啓!

總結:
mybatis配置階段,使用了共享模式、裝飾模式、代理模式、工廠模式、模板模式、外觀模式。
插件的原理是使用代理(jdk代理(必須要有接口)或者cglib代理(類),兩者性能都很高),用jdk的動態代理實現了攔截器和Mapper接口。這種動態代理和註解的運用也是非常值得學習的。
好吧!寫的很爛,連圖都沒畫!
下一篇準備mybatis執行部分解析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章