溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Mybatis源碼分析之如何理解SQLSession初始化

發(fā)布時(shí)間:2021-10-21 16:48:15 來源:億速云 閱讀:153 作者:iii 欄目:編程語言

這篇文章主要介紹“Mybatis源碼分析之如何理解SQLSession初始化”,在日常操作中,相信很多人在Mybatis源碼分析之如何理解SQLSession初始化問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Mybatis源碼分析之如何理解SQLSession初始化”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

這次打算寫一個(gè) Mybatis 源碼分析的系列,大致分為

  • Mybatis 啟動(dòng)流程分析

  • Mybatis 的SQL 執(zhí)行流程分析

  • Mybatis 的拓展點(diǎn)以及與 Spring Boot 的整合

這篇文章先來分析 Mybati初始化流程,如何讀取配置文件到,以及創(chuàng)建出 SqlSession 示例.主要內(nèi)容包括

  • 讀取、解析mybatis 全局配置文件

  • 映射 mapper.java 文件

  • 解析 mapper.xml 文件

  • 解析 mapper.xml 各個(gè)節(jié)點(diǎn)配置,包括 namespace、緩存、增刪改查節(jié)點(diǎn)

  • Mybatis 緩存機(jī)制

  • 構(gòu)建DefaultSqlSessionFactory

什么是 SQLSession

SQLSession對外提供了用戶和數(shù)據(jù)庫之間交互需要的所有方法,隱藏了底層的細(xì)節(jié)。默認(rèn)實(shí)現(xiàn)類是DefaultSqlSession

SQLSession 創(chuàng)建示例

通過一個(gè)mybatis 官方提供的示例,看下如何手動(dòng)創(chuàng)建 SQLSession

//Mybatis 配置文件,通常包含:數(shù)據(jù)庫連接信息,Mapper.class 全限定名包路徑,事務(wù)配置,插件配置等等
String resource = "org/mybatis/builder/mybatis-config.xml";
//以輸入流的方式讀取配置
InputStream inputStream = Resources.getResourceAsStream(resource);
//實(shí)例化出 SQLSession 的必要步驟 SqlSessionFactoryBuilder --> SqlSessionFactory  --> SqlSession
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
SqlSession session = factory.openSession();

通過輸入流讀取mybatis-config.xml 配置文件

接下來就通過new SqlSessionFactoryBuilder() 開始我們的構(gòu)建 SQLSession 源碼分析

//SqlSessionFactory 有4 個(gè)構(gòu)造方法,最終都會(huì)執(zhí)行到全參的構(gòu)造方法
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
  try {
    //首先會(huì)實(shí)例化一個(gè) XMLConfigBuilder ,這里先有個(gè)基本的認(rèn)知:XMLConfigBuilder 就是用來解析 XML 文件配置的
    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
    //經(jīng)過parser.parse()之后,XML配置文件已經(jīng)被解析成了Configuration ,Configuration 對象是包含著mybatis的所有屬性.
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
   //... 關(guān)閉流,拋異常
  }
}
插句嘴,先看下XMLConfigBuilder 類圖

Mybatis源碼分析之如何理解SQLSession初始化

  • MLConfigBuilder : 解析全局配置文件即 mybatis-config.xml

  • XMLMapperBuilder : 解析 Mapper 文件,配置在mybatis-config.xml 文件中 mapper.java 的包路徑

  • XMLStatementBuilder :解析 mapper 文件的節(jié)點(diǎn)中 ,SQL 語句標(biāo)簽:select,update,insert,delete

  • SQLSourceBuilder:動(dòng)態(tài)解析 SQL 語句,根據(jù) SqlNode 解析 Sql 語句中的標(biāo)簽,比如<trim>,<if>等標(biāo)簽

當(dāng)然 BaseBuilder 的實(shí)現(xiàn)類不僅這 4 個(gè),這里只介紹這 4 類,在后續(xù)一步步分析中都能看到這幾個(gè)的身影 點(diǎn)進(jìn)去看一下 parser.parse()

public Configuration parse() {
  // 若已經(jīng)解析過了 就拋出異常
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  // 設(shè)置解析標(biāo)志位
  parsed = true;
  // 解析mybatis-config.xml的節(jié)點(diǎn),讀取配置文件,加載到 Configuration 中 
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

通過XPathParser 對象來解析 xml 文件成XNode 對象

解析成 Configuration 成之前會(huì)先將 xml 配置文件解析成 XNode 對象

public XNode evalNode(Object root, String expression) {
  //mybatis 自已定義了一個(gè)XPathParser 對象來解析 xml ,其實(shí)對Document做了封裝 
  Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
  if (node == null) {
    return null;
  }
  return new XNode(this, node, variables);
}

解析config.xml 中的各個(gè)節(jié)點(diǎn)

接下來就看看下mybatis 是如何一步步讀取配置文件的

/**
 * 解析 mybatis-config.xml的 configuration節(jié)點(diǎn)
 * 解析 XML 中的各個(gè)節(jié)點(diǎn)
 */
private void parseConfiguration(XNode root) {
  try {
    /**
     * 解析 properties節(jié)點(diǎn)
     *     <properties resource="mybatis/db.properties" />
     *     解析到org.apache.ibatis.parsing.XPathParser#variables
     *     org.apache.ibatis.session.Configuration#variables
     */
    propertiesElement(root.evalNode("properties"));
    /**
     * 解析我們的mybatis-config.xml中的settings節(jié)點(diǎn)
     * 具體可以配置哪些屬性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
     * <settings>
          <setting name="cacheEnabled" value="true"/>
          <setting name="lazyLoadingEnabled" value="true"/>
         <setting name="mapUnderscoreToCamelCase" value="false"/>
         <setting name="localCacheScope" value="SESSION"/>
         <setting name="jdbcTypeForNull" value="OTHER"/>
          ..............
         </settings>
     *
     */
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    /**
     * 基本沒有用過該屬性
     * VFS含義是虛擬文件系統(tǒng);主要是通過程序能夠方便讀取本地文件系統(tǒng)、FTP文件系統(tǒng)等系統(tǒng)中的文件資源。
       Mybatis中提供了VFS這個(gè)配置,主要是通過該配置可以加載自定義的虛擬文件系統(tǒng)應(yīng)用程序
       解析到:org.apache.ibatis.session.Configuration#vfsImpl
     */
    loadCustomVfs(settings);
    /**
     * 指定 MyBatis 所用日志的具體實(shí)現(xiàn),未指定時(shí)將自動(dòng)查找。
     * SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
     * 解析到org.apache.ibatis.session.Configuration#logImpl
     */
    loadCustomLogImpl(settings);
    /**
     * 解析我們的別名
     * <typeAliases>
         <typeAlias alias="User" type="com.xxx.entity.User"/>
      </typeAliases>
     <typeAliases>
        <package name="com.xxx.use"/>
     </typeAliases>
     解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
     */
    typeAliasesElement(root.evalNode("typeAliases"));
    /**
     * 解析我們的插件(比如分頁插件)
     * mybatis自帶的
     * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
       ParameterHandler (getParameterObject, setParameters)
       ResultSetHandler (handleResultSets, handleOutputParameters)
       StatementHandler (prepare, parameterize, batch, update, query)
      解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
     */
    pluginElement(root.evalNode("plugins"));

    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    // 設(shè)置settings 和默認(rèn)值
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631

    /**
     * 解析我們的mybatis環(huán)境,解析 DataSource
       <environments default="dev">
         <environment id="dev">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
           <property name="driver" value="${jdbc.driver}"/>
           <property name="url" value="${jdbc.url}"/>
           <property name="username" value="root"/>
           <property name="password" value="Zw726515"/>
           </dataSource>
         </environment>

       <environment id="test">
         <transactionManager type="JDBC"/>
         <dataSource type="POOLED">
         <property name="driver" value="${jdbc.driver}"/>
         <property name="url" value="${jdbc.url}"/>
         <property name="username" value="root"/>
         <property name="password" value="123456"/>
         </dataSource>
       </environment>
     </environments>
     *  解析到:org.apache.ibatis.session.Configuration#environment
     *  在集成spring情況下由 spring-mybatis提供數(shù)據(jù)源 和事務(wù)工廠
     */
    environmentsElement(root.evalNode("environments"));
    /**
     * 解析數(shù)據(jù)庫廠商
     *     <databaseIdProvider type="DB_VENDOR">
              <property name="SQL Server" value="sqlserver"/>
              <property name="DB2" value="db2"/>
              <property name="Oracle" value="oracle" />
              <property name="MySql" value="mysql" />
           </databaseIdProvider>
     *  解析到:org.apache.ibatis.session.Configuration#databaseId
     */
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    /**
     * 解析我們的類型處理器節(jié)點(diǎn)
     * <typeHandlers>
          <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
        </typeHandlers>
        解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
     */
    typeHandlerElement(root.evalNode("typeHandlers"));
    /**
     * 最最重要的就是解析我們的mapper
     *
     resource:來注冊我們的class類路徑下的
     url:來指定我們磁盤下的或者網(wǎng)絡(luò)資源的
     class:
     若注冊Mapper不帶xml文件的,這里可以直接注冊
     若注冊的Mapper帶xml文件的,需要把xml文件和mapper文件同名 同路徑
     -->
     <mappers>
        <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
        <mapper class="com.tuling.mapper.DeptMapper"></mapper>


          <package name="com.tuling.mapper"></package>
        -->
     </mappers>
     * 解析 mapper:
     * 1.解析mapper.java接口 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers
     * 2.解析 mapper.xml 配置
     */
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

解析 mapper 文件

解析 mapper.java 接口到knowMappers 中

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    //獲取我們mappers節(jié)點(diǎn)下的一個(gè)一個(gè)的mapper節(jié)點(diǎn)
    for (XNode child : parent.getChildren()) {
      /**
         * 指定 mapper 的 4 中方式:
         * 1.指定的 mapper 所在的包路徑,批量注冊
         * 2.通過 resource 目錄指定
         * 3.通過 url 指定,從網(wǎng)絡(luò)資源或者本地磁盤
         * 4.通過 class 路徑注冊
         */
      //判斷我們mapper是不是通過批量注冊的
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        //判斷從classpath下讀取我們的mapper
        String resource = child.getStringAttribute("resource");
        //判斷是不是從我們的網(wǎng)絡(luò)資源讀取(或者本地磁盤得)
        String url = child.getStringAttribute("url");
        //解析這種類型(要求接口和xml在同一個(gè)包下)
        String mapperClass = child.getStringAttribute("class");

        //解析 mapper 文件
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          // 把mapper文件讀取出一個(gè)流,是不是似曾相識(shí),最開始的時(shí)候讀取mybatis-config.xml 配置文件也是通過輸入流的方式讀取的
          InputStream inputStream = Resources.getResourceAsStream(resource);
          //創(chuàng)建讀取XmlMapper構(gòu)建器對象,用于來解析我們的mapper.xml文件,上面提到過的 XMLMapperBuilder對象
          /**
           * 讀取的 mapper 文件會(huì)被放入到 MapperRegistry 中的 knownMappers中
           * Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
           * 為后續(xù)創(chuàng)建 Mapper 代理對象做準(zhǔn)備
           */
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          //真正的解析我們的mapper.xml配置文件,這里就會(huì)來解析我們的sql
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

解析 mapper.xml 文件

解析Mapper.xml 中的 SQL 標(biāo)簽

//解析的 SQL 語句節(jié)點(diǎn)會(huì)放在Configuration.MappedStatement.SqlSource 中,SqlSource 中包含了一個(gè)個(gè)的 SQLNode,一個(gè)標(biāo)簽對應(yīng)一個(gè) SQLNode
public void parse() {
  //判斷當(dāng)前的Mapper是否被加載過
  if (!configuration.isResourceLoaded(resource)) {
    //真正的解析我們的mapper
    configurationElement(parser.evalNode("/mapper"));
    //把資源保存到我們Configuration中
    configuration.addLoadedResource(resource);

    bindMapperForNamespace();
  }
  
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

解析 mapper.xml 中的各個(gè)節(jié)點(diǎn)

//解析我們的<mapper></mapper>節(jié)點(diǎn)
private void configurationElement(XNode context) {
  try {
    /**
     * 解析我們的namespace屬性
     * <mapper namespace="com.xx.mapper.xxxMapper">
     */
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    //保存我們當(dāng)前的namespace  并且判斷接口完全類名==namespace
    builderAssistant.setCurrentNamespace(namespace);
    /**
     * 解析我們的緩存引用
     * 說明我當(dāng)前的緩存引用和DeptMapper的緩存引用一致
     * <cache-ref namespace="com.xx.mapper.xxxMapper"></cache-ref>
          解析到org.apache.ibatis.session.Configuration#cacheRefMap<當(dāng)前namespace,ref-namespace>
          異常下(引用緩存未使用緩存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
     */
    cacheRefElement(context.evalNode("cache-ref"));
    /**
     * 解析我們的cache節(jié)點(diǎn)
     * <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
        解析到:org.apache.ibatis.session.Configuration#caches
               org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
     */
    cacheElement(context.evalNode("cache"));
    /**
     * 解析paramterMap節(jié)點(diǎn)
     */
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    /**
     * 解析我們的resultMap節(jié)點(diǎn)
     * 解析到:org.apache.ibatis.session.Configuration#resultMaps
     *    異常 org.apache.ibatis.session.Configuration#incompleteResultMaps
     *
     */
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    /**
     * 解析我們通過sql節(jié)點(diǎn)
     *  解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
     *   其實(shí)等于 org.apache.ibatis.session.Configuration#sqlFragments
     *   因?yàn)樗麄兪峭灰茫跇?gòu)建XMLMapperBuilder 時(shí)把Configuration.getSqlFragments傳進(jìn)去了
     */
    sqlElement(context.evalNodes("/mapper/sql"));
    /**
     * 解析我們的select | insert |update |delete節(jié)點(diǎn)
     * 解析到org.apache.ibatis.session.Configuration#mappedStatements
     * 最終SQL節(jié)點(diǎn)會(huì)被解析成 MappedStatement,一個(gè)節(jié)點(diǎn)就是對應(yīng)一個(gè)MappedStatement
     * 準(zhǔn)確的說 sql 節(jié)點(diǎn)被解析成 SQLNode 封裝在 MappedStatement.SqlSource 中
     * SQLNode 對應(yīng)的就是 sql 節(jié)點(diǎn)中的子標(biāo)簽,比如<trim>,<if>,<where> 等
     */
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

著重分析幾個(gè)解析過程

解析緩存
  private void cacheElement(XNode context) {
    if (context != null) {
    /**
     * cache元素可指定如下屬性,每種屬性的指定都是針對都是針對底層Cache的一種裝飾,采用的是裝飾器的模式
     * 緩存屬性:
     * 1.eviction: 緩存過期策略:默認(rèn)是LRU
     *      LRU – 最近最少使用的:移除最長時(shí)間不被使用的對象。--> LruCache
     *      FIFO – 先進(jìn)先出:按對象進(jìn)入緩存的順序來移除它們。--> FifoCache
     *      SOFT – 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象。--> SoftCache
     *      WEAK – 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象。--> WeakCache
     * 2.flushInterval: 清空緩存的時(shí)間間隔,單位毫秒,默認(rèn)不清空,指定了之后將會(huì)用 ScheduleCache 封裝
     * 3.size :緩存對象的大小,默認(rèn)是 1024,其是針對LruCache而言的,LruCache默認(rèn)只存儲(chǔ)最多1024個(gè)Key
     * 4.readOnly :默認(rèn)是false,底層SerializedCache包裝,會(huì)在寫緩存的時(shí)候?qū)⒕彺鎸ο筮M(jìn)行序列化,然后在讀緩存的時(shí)候進(jìn)行反序列化,這樣每次讀到的都將是一個(gè)新的對象,即使你更改了讀取到的結(jié)果,也不會(huì)影響原來緩存的對象;true-給所有調(diào)用者返回緩存對象的相同實(shí)例
     * 5.blocking : 默認(rèn)為false,當(dāng)指定為true時(shí)將采用BlockingCache進(jìn)行封裝,在進(jìn)行增刪改之后的并發(fā)查詢,只會(huì)有一條去數(shù)據(jù)庫查詢,而不會(huì)并發(fā)訪問
     * 6.type: type屬性用來指定當(dāng)前底層緩存實(shí)現(xiàn)類,默認(rèn)是PerpetualCache,如果我們想使用自定義的Cache,則可以通過該屬性來指定,對應(yīng)的值是我們自定義的Cache的全路徑名稱
     */
      //解析cache節(jié)點(diǎn)的type屬性
      String type = context.getStringAttribute("type", "PERPETUAL");
      //根據(jù)type的String獲取class類型
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      //獲取緩存過期策略:默認(rèn)是LRU
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      //flushInterval(刷新間隔)屬性可以被設(shè)置為任意的正整數(shù),設(shè)置的值應(yīng)該是一個(gè)以毫秒為單位的合理時(shí)間量。 默認(rèn)情況是不設(shè)置,也就是沒有刷新間隔,緩存僅僅會(huì)在調(diào)用語句時(shí)刷新。
      Long flushInterval = context.getLongAttribute("flushInterval");
      //size(引用數(shù)目)屬性可以被設(shè)置為任意正整數(shù),要注意欲緩存對象的大小和運(yùn)行環(huán)境中可用的內(nèi)存資源。默認(rèn)值是 1024。
      Integer size = context.getIntAttribute("size");
      //只讀)屬性可以被設(shè)置為 true 或 false。只讀的緩存會(huì)給所有調(diào)用者返回緩存對象的相同實(shí)例。 因此這些對象不能被修改。這就提供了可觀的性能提升。而可讀寫的緩存會(huì)(通過序列化)返回緩存對象的拷貝。 速度上會(huì)慢一些,但是更安全,因此默認(rèn)值是 false
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      //把緩存節(jié)點(diǎn)加入到Configuration中
      //這里的 builder()方法利用責(zé)任鏈方式循環(huán)實(shí)例化Cache 對象
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }
Mybatis 緩存機(jī)制
MyBatis自帶的緩存有一級(jí)緩存和二級(jí)緩存

一級(jí)緩存

Mybatis一級(jí)緩存是指Session緩存。作用域默認(rèn)是一個(gè)SqlSession。默認(rèn)開啟一級(jí)緩存,范圍有SESSION和STATEMENT兩種,默認(rèn)是SESSION,如果需要更改一級(jí)緩存的范圍,可以在Mybatis的配置文件中,通過localCacheScope指定

<setting name="localCacheScope" value="STATEMENT"/>

二級(jí)緩存

Mybatis的二級(jí)緩存是指mapper映射文件。二級(jí)緩存的作用域是同一個(gè)namespace下的mapper映射文件內(nèi)容,多個(gè)SqlSession共享。二級(jí)緩存是默認(rèn)啟用的,但是需要手動(dòng)在 mapper 文件中設(shè)置啟動(dòng)二級(jí)緩存

//在 mapper.xml 文件加上此配置,該 mapper 文件對應(yīng)的 SQL就開啟了緩存
  <cache />

或者直接關(guān)閉緩存

//在全局配置文件中關(guān)閉緩存
<settings>
  <setting name="cacheEnabled" value="false" />
</settings>

注意:如果開啟了二級(jí)緩存,查詢結(jié)果的映射對象一定要實(shí)現(xiàn)Serializable ,因?yàn)閙ybatis 緩存對象的時(shí)候默認(rèn)是會(huì)對映射對象進(jìn)行序列號(hào)操作的

解析select | insert |update |delete節(jié)點(diǎn)
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  //循環(huán)我們的select|delte|insert|update節(jié)點(diǎn)
  for (XNode context : list) {
    //創(chuàng)建一個(gè)xmlStatement的構(gòu)建器對象
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      //通過該步驟解析之后 mapper.xml 的 sql 節(jié)點(diǎn)就也被解析了  
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

至此配置mybatis 的配置文件已經(jīng)解析完成,配置文件已經(jīng)解析成了Configuration,會(huì)到最初,我們的目標(biāo)是獲取 SqlSession 對象,通過new SqlSessionFactoryBuilder().build(reader) 已經(jīng)構(gòu)建出了一個(gè)SqlSessionFactory 工廠對象,還差一步 SqlSession session = sqlMapper.openSession();

根據(jù) Configuration build 出 DefaultSqlSessionFactory

通過分析DefaultSqlSession 的 openSession() 來實(shí)例化 SQLSession 對象

//從session中開啟一個(gè)數(shù)據(jù)源
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    //獲取環(huán)境變量
    final Environment environment = configuration.getEnvironment();
    // 獲取事務(wù)工廠
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    /**
     * 創(chuàng)建一個(gè)sql執(zhí)行器對象
     * 一般情況下 若我們的mybaits的全局配置文件的cacheEnabled默認(rèn)為ture就返回
     * 一個(gè)cacheExecutor,若關(guān)閉的話返回的就是一個(gè)SimpleExecutor
     */
    final Executor executor = configuration.newExecutor(tx, execType);
    //創(chuàng)建返回一個(gè)DeaultSqlSessoin對象返回
    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();
  }
}

仔細(xì)一點(diǎn)看你會(huì)發(fā)現(xiàn)configuration 就是剛才千辛萬苦創(chuàng)建出來的 Configuration 對象,包含所有 mybatis 配置信息.至此SQLSession 的創(chuàng)建已分析完畢.

總結(jié)

總結(jié)一下上述流程:

第一步:通過輸入流讀取mybatis-config.xml 配置文件

1:通過XPathParser 讀取xml 配置文件成 XNode 屬性 2:通過 XMLConfigBuilder 解析 mybatis-config.xml 中的各個(gè)節(jié)點(diǎn)配置,包括

  • 解析properties 節(jié)點(diǎn)

  • 解析settings 節(jié)點(diǎn)

  • 加載日志框架

  • 解析 typeAliases

  • 解析拓展插架 plugins

  • 解析數(shù)據(jù)源 DataSource

  • 解析類型處理器 typeHandle

  • 解析 mapper文件

第二步:讀取 mapper.java 類

讀取方式有 package,resource,url,class ,最終都會(huì)放入到 Map<Class<?>, MapperProxyFactory<?>> knownMappers 中

第三步: 讀取 mapper.xml 節(jié)點(diǎn)

1.同樣以輸入流的方式讀取 mapper.xml 文件 2.通過 XMLMapperBuilder 實(shí)例解析 mapper.xml 文件中各個(gè)接點(diǎn)屬性

  • 解析 namespace 屬性

  • 解析緩存引用 cache-ref

  • 解析 cache 節(jié)點(diǎn)

  • 解析 resultMap 節(jié)點(diǎn)

  • 解析 sql 節(jié)點(diǎn)

  • 解析 select | insert |update |delete節(jié)點(diǎn) 3.通過 XMLStatementBuilder 解析SQL 標(biāo)簽

第四步:將所有配置屬性都封裝到 Configuration 對象中,構(gòu)建出SqlSessionFactory 工廠實(shí)例
第五步:從session中開啟一個(gè)數(shù)據(jù)源 SqlSessionFactory#openSession(),默認(rèn)是 DefaultSqlSession

到此,關(guān)于“Mybatis源碼分析之如何理解SQLSession初始化”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI