Mybatis的 SqlSessionFactory 初始化過程 和SqlSession 初始化過程

用了幾年的Mybatis,但是一直沒有時間去研究下這個框架,當然這段時間也找了事件大概的看了一遍Mybatis主要功能的源碼。
總體上感覺Mybatis 屬於小巧功能卻十分強大的框架,個人以爲Mybatis應該作爲Java初學者第一個閱讀的框架源碼。有興趣可以跟着博主思路讀讀源碼>-<

本文將以以下幾個問題展開:

  1. Mybatis 運行機制是怎樣的?
  2. Mybatis 初始化中,如何獲取SqlSessionFactory的?
  3. Mybatis 中,各大組件是通過怎樣的形式串起來的?
  4. JDBC 的幾種事務級別都各是什麼?
  5. 創建 SqlSession時候,有哪些參數可以傳?
  6. Mybatis有哪些事務類型?

簡介

MyBatis 是一款基於Java的優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java對象)映射成數據庫中的記錄。

使用上官網並沒有提供太多samples,不過提供了很多配置意思,這個可以幫助我們在一些情況下優化代碼及減少一些bug。

https://mybatis.org/mybatis-3/zh/index.html

例子

以API 方式作爲入口去了解框架運行原理是一種非常好的方式,本文例子以官網例子變形作爲分析的起點:

    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.getByIndex(11);
            log.info("user :{}", user);
        }
    }

相關xml配置:

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1/df?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Asia/Shanghai&amp;allowMultiQueries=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

具體例子可以看 https://github.com/anLA7856/mybatislearn

通過InputStream 獲取到指定輸入流的xml配置文件後,則進入了 SqlSessionFactory 的表演時間。

SqlSessionFactorySqlSessionFactoryBuild 構建,SqlSessionFactoryBuild 中有多個不同的build方法,主要有三種類型參數:

  1. Reader
  2. String enviroment
  3. Properties properties
  4. InputStream
  5. Configuration

InputStreamReader指傳入是字節流還是字符流去解析不同格式的輸入流信息,而 enviromentproperties 則是傳入解析的具體配置,可以指定在xml中只返回哪個環境(enviroment) 的配置,或者完全覆蓋屬性(properties)。而Configuration則是Mybatis的核心對象,整個解析過程出的數據都會放入到Configuration中。

在選擇完方法之後,Mybatis就會進入到自己實現的XML解析類XMLConfigBuilder 中,通過XPathParser 作爲具體解析器,將解析出的xml存儲在org.w3c.dom.Document中:

  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
  public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(inputStream));
  }
  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;
  }

XMLConfigBuilder 父類 BaseBuilder 中,有三個變量:

  1. Configuration configuration:Mybatis中核心配置中心
  2. TypeAliasRegistry typeAliasRegistry:別名配置中心,以Map<String, Class<?>> 存儲Mybatis配置別名和類對象映射,例如:registerAlias("byte", Byte.class);
  3. TypeHandlerRegistry typeHandlerRegistry:類型處理配置中心,即值該怎樣傳遞給JDBC,又怎樣從JDBC中拿出來?以 Map<JdbcType, TypeHandler<?>> 存儲。例如Java對象String,對應數據庫可以有多種數據類型varchar, longvarchar, nchar 等。二者交互就是通過TypeHandler來處理的。

當 通過 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 解析後,開始由 build(parser.parse()); 將解析後xml依據Mybatis需要組裝起來。

Configuration

整個SqlSessionFactory 初始化過程,在我看來一句話描述就是:
Mybatis通過 其內部定義的強大的XML解析,將配置的xml文件和Mapper文件全部解析成爲Java類對象形式,並且以Configuration爲中心。

當然裏面的配置以及存儲對象很多。

根據上面源碼一路往下,進入到XMLConfigBuilderparseConfiguration方法:

 private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(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);
    }
  }

上面代碼中,從xml配置中解析了configuration 節點內容,而後根據節點對Configuration 相關屬性進行填充:

  1. 讀取properties 內容,將其放入到ConfigurationProperties variables 中。
  2. 讀取settings內容,如果有則放到 ConfigurationClass<? extends VFS> vfsImpl ,並且會加入到VFSVFS.addImplClass(this.vfsImpl);
    VFS 是mybatis提供的一個接口,用於在服務器中訪問資源,即可以利用vfs訪問內部文件
  3. 讀取settings內容,讀取logImpl 配置,將 其設置到 ConfigurationlogImpl ,以及使用 初始化 LogFactory 的用戶配置日誌工廠LogFactory.useCustomLogging(this.logImpl);
  4. 讀取別名配置,嚷道 ConfigurationTypeAliasRegistry 中。
  5. 讀取 攔截器配置,放入 ConfigurationInterceptorChain
  6. 配置ObjectFactory,並放入Configuration,Mybatis將會用它來創建所有的需要的Object
  7. 配置 ObjectWrapperFactory,Mybatis沒有默認實現。博主暫時沒發現其用途,因爲Mybatis默認實現比較簡單,hasWrapperFor 返回false,而getWrapperFor 則拋出了異常。
  8. 配置 ReflectorFactory ,用於作用反射工廠
  9. 讀取並存儲所有settings節點內容到 Configuration的不同配置中
  10. 配置事務工廠TransactionFactory以及數據源工廠 DataSourceFactory
  11. 配置databaseIdProvider,同樣存儲到 Configuration
  12. 配置 typeHandlers,放入到 Configuration
  13. 配置mappers 節點,加載所有的mappers的xml配置。
  • 使用 XMLMapperBuilder 加載 對應mappers配置
  • 使用 Set<String> 來存儲已經加載過的配置
  • 使用 MapperBuilderAssistant 保存 緩存,或者使用別處或者當前自己緩存,以命名空間id爲標識,或者是ParameterMapping,最後交由的都是Configuration 保管。
  • 解析 cache、/mapper/parameterMap、/mapper/resultMap、/mapper/sql、select|insert|update|delete 等內容
  • 解析完成後,存儲 Set<String> 類型的 loadedResources 和 將 MapperRegistry 存入 mapperRegistry 中。

SqlSession 初始化過程

讀取完配置,構建完成 SqlSessionFactory ,就到了SqlSession 的構造事件,SqlSessionFactory 提供了以下幾種獲取方法:

  • public SqlSession openSession()
  • public SqlSession openSession(boolean autoCommit)
  • public SqlSession openSession(boolean autoCommit)
  • public SqlSession openSession(ExecutorType execType)

  • openSession基於三個參數的多個重載方法:
  • ExecutorType:執行方式,分爲 SIMPLE, REUSE, BATCH
  • TransactionIsolationLevel level:設置事務級別NONE、READ_COMMITTED、READ_UNCOMMITTED、REPEATABLE_READ、SERIALIZABLE
  • autoCommit:設置自動提交,分truefalse
  1. ExecutorType,默認是 SIMPLE,
  • SIMPLE:即每次用完Statement 後,都會關閉Statement,而後下次由重新打開
  • REUSE: 會將 Statement 存儲到 Map<String, Statement> 中,keyboundSql.getSql()
  • BATCH:這種執行方式,主要用於批量操作,每次執行將statement預存到有序集合,主要用於循環或者多次執行構建一個存儲過程或批處理過程
  1. TransactionIsolationLevel,事務級別,對應jdbc規範中的集中食物級別
  • NONE:沒有事務,對應 JDBC中 Connection.TRANSACTION_NONE
  • READ_COMMITTED: 禁止髒讀,允許不可重複讀和幻讀。讀已提交的,禁止事務讀未提交行。會對相應的行加索,如果是範圍讀取例如i>100,InnoDB使用間隙鎖或下一鍵(間隙加索引記錄)鎖來鎖定掃描的索引範圍,以阻止其他會話插入範圍所涵蓋的間隙。(會出現不可重複讀,例如此時其他session commit了一次操作,那麼當前事務還是能夠讀取到)
  • READ_UNCOMMITTED:允許髒讀,不可重複讀和幻讀。一個事務可以讀取到另一個事務未提交的行(髒讀)。如果另一個事務回滾,則當前事務則會讀到一個不存在的行(風險)。非鎖定方式。
  • REPEATABLE_READ:禁止髒讀和不可重複讀,允許幻讀。這是Innodb默認事務級別。不允許出現以下情況:
    i:事務讀取一個未提交的更改行
    ii:同樣不允許當一個事務讀取行而另一個事務嘗試修改行
    iii:禁止一個事務兩次讀取獲取不同結果(不可重複讀)
    理解上來說,當有A,B事務,當B事務設爲REPEATABLE_READ時,那麼B這個事務中,不管查詢多少次某一個內容,都是一樣的。
    無論是否其他事務對其已經做了提交性的更改。Innodb對其處理是後面查詢都只讀第一次的快照。(這樣會出現幻讀)
  • SERIALIZABLE :禁止髒讀,不可重複讀和幻讀。最嚴格的。
  • Mysql可以使用 select @@tx_isolation; 查詢當前事務隔離級別。
  • 設置事務隔離級別:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  1. 如果自動提交,那麼每一行語句都會自動提交,Mysql中查看方式爲:select @@autocommit;,關閉當前session的自動提交爲:
    set autocommit = off;

下面看 DefaultSqlSessionFactoryopenSessionFromDataSource

  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();
    }
  }

上面代碼有以下幾種意思:

  1. 通過Environment獲取事務類型,Mybatis有兩種事務提供:
  • JdbcTransaction,使用Jdbc的事務控制,就是上面講的四種事務隔離級別,並且直接通過使用JDBC的commit和rollback功能,延遲獲取連接知道有需要。當設定了 autocommit,那麼會忽略commitrollback
  • ManagedTransaction:讓容器管理整個事務的生命週期,延遲獲取連接。忽略所有的 commit和rollback請求。
  1. newExecutor 中,使用事務創建一個執行器,默認的ExecutorTypeSIMPLE 類型,如果沒有更改默認開啓的二級緩存,則會將所有的執行器都包裝在一個
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }

CachingExecutor裏面。

  • 一級緩存是指 在單個SqlSession中的緩存,可以在
  • 二級緩存是可以跨SqlSession 共享的緩存
  • Mybatis中獲取操作爲 二級緩存->一級緩存->數據庫
  • 其實最好不要在Mybatis中開啓緩存,使用它作爲純粹的ORM框架就好了
    而後,會將所有配置的過濾器串成鏈:
executor = (Executor) interceptorChain.pluginAll(executor);

遍歷所有interceptors,而後拿到屬於 Executor 的過濾器鏈,並封裝返回:


  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
  1. 將獲取的Executor 包裝稱爲一個DefaultSqlSession 並返回。

整個 SqlSessionFactory 初始化過程 和SqlSession 初始化過程 已分析完。
下篇文章將開始圍繞Mysql插件以及研究Mysql一級、二級緩存相關>-<

覺得博主寫的有用,不妨關注博主公衆號: 六點A君。
哈哈哈,一起研究Mybatis:
在這裏插入圖片描述

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