天天看點

mybatis_3源碼閱讀日記_容器的加載與初始化MyBatis整體架構MyBatis的主要成員SqlSessionFactory的建立

MyBatis整體架構

Mybatis的功能架構分為三層

API接口層,資料處理層,基礎支援層

API接口層

核心對象就是SqlSession,它是上層應用和Mybatis打交道的橋梁,也有的人稱之為大門,SqlSession中定義了非常多的對資料庫操作的方法,接口層在接受到調用請求時,會調用核心處理層的相應子產品來完成具體的資料庫操作

資料處理層

這一層主要就是跟資料庫操作相關的動作都是在這資料處理層完成的。

核心處理層主要做了這4件事:

  • 把接口中傳入的參數解析并映射成JDBC類型;
  • 解析xml檔案中的SQL語句,包括插入參數和動态SQL的生成;
  • 執行SQL語句;
  • 處理結果集,并映射成Java對象。

插件也屬于核心層,這是由它的工作方式和攔截的對象決定的。

基礎支撐層

負責最基礎的功能支撐,包括連接配接管理、事務管理、配置加載和緩存處理,這些都是共用的東西,将他們抽取出來作為最基礎的元件。為上層的資料處理層提供最基礎的支撐。

mybatis_3源碼閱讀日記_容器的加載與初始化MyBatis整體架構MyBatis的主要成員SqlSessionFactory的建立
mybatis_3源碼閱讀日記_容器的加載與初始化MyBatis整體架構MyBatis的主要成員SqlSessionFactory的建立

MyBatis的主要成員

Configuration

MyBatis所有的配置資訊都儲存在Configuration對象之中,配置檔案中的大部配置設定置都會存儲到該類中。

SqlSession

作為MyBatis工作的主要頂層API,表示和資料庫互動時的會話,完成必要資料庫增删改查功能。

Executor

MyBatis執行器,是MyBatis 排程的核心,負責SQL語句的生成和查詢緩存的維護。

StatementHandler

封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設定參數等。

ParameterHandler

負責對使用者傳遞的參數轉換成JDBC Statement 所對應的資料類型。

ResultSetHandler

負責将JDBC傳回的ResultSet結果集對象轉換成List類型的集合。

TypeHandler

負責java資料類型和jdbc資料類型(也可以說是資料表列類型)之間的映射和轉換。

MappedStatement

MappedStatement維護一條<select|update|delete|insert>節點的封裝。

SqlSource

負責根據使用者傳遞的parameterObject,動态地生成SQL語句,将資訊封裝到BoundSql對象中,并傳回

BoundSql

表示動态生成的SQL語句以及相應的參數資訊。

SqlSessionFactory的建立

SqlSessionFactory是通過SqlSessionFactoryBuilder工廠類建立的,而不是直接使用構造器。

SqlSessionFactoryBuilder的主要代碼如下:

//SqlSessionFactoryBuilder是一個建造者模式
 public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
  
 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
 
 public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

           

SqlSessionFactory提供了根據位元組流、字元流以及直接使用org.apache.ibatis.session.Configuration配置類三種途徑的讀取配置資訊方式,但究其根本都是首先将XML配置檔案建構為Configuration配置類,然後将Configuration設定到SqlSessionFactory預設實作DefaultSqlSessionFactory的configurationz字段并傳回。主要解析配置檔案的邏輯都委托給XMLConfigBuilder了。

XMLConfigBuilder的主要代碼

public class XMLConfigBuilder extends BaseBuilder {
	....
	  /**
	   * Description 構造XMLConfigBuilder
	   * @param reader 流
	   * @param environment 
	   * @param props
	   * @author songhongwei s19744
	  * @time 202/1/5 16:44
	   **/
	  public XMLConfigBuilder(Reader reader, String environment, Properties props) {
	    //XPathParser用于SAX解析xml,解析器寫死為XMLMapperEntityResolver
	    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
	  }
	  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
	    //建立Configuration對象,用來儲存配置檔案的配置
	    super(new Configuration());
	    ErrorContext.instance().resource("SQL Mapper Configuration");
	    this.configuration.setVariables(props);
	    this.parsed = false;
	    this.environment = environment;
	    this.parser = parser;
	  }
	....
}
           

标簽解析類

public class XPathParser {
 private final Document document;
 private boolean validation;
 //XML标簽解析器,可使用不通的解析器,通過構造方法傳進來,使用到了政策模式
 private EntityResolver entityResolver;
 private Properties variables;
 private XPath xpath;
    /**
    * Description 構造方法
    *
    * @param reader
    *            Reader
    * @param validation
    *            是否進行DTD 校驗
    * @param variables
    *            屬性配置
    * @param entityResolver 
    *            XML實體節點解析器
    * @author songhongwei s19744
    * @time 202/1/5 19:54
    **/
 public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
   commonConstructor(validation, variables, entityResolver);
   this.document = createDocument(new InputSource(reader));
 }
 
 //建立document
 private Document createDocument(InputSource inputSource) {
   // important: this must only be called AFTER common constructor
  try {
           DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
           factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
           factory.setValidating(validation);
           // 設定由本工廠建立的解析器是否支援XML命名空間 xmlns
           factory.setNamespaceAware(false);
           factory.setIgnoringComments(true);
           factory.setIgnoringElementContentWhitespace(false);
           // 設定是否将CDATA節點轉換為Text節點
           factory.setCoalescing(false);
           // 設定是否展開實體引用節點,這裡應該是sql片段引用的關鍵
           factory.setExpandEntityReferences(true);

           DocumentBuilder builder = factory.newDocumentBuilder();
           // 設定解析mybatis xml文檔節點的解析器,也就是上面的XMLMapperEntityResolver
           builder.setEntityResolver(entityResolver);
           builder.setErrorHandler(new ErrorHandler() {
               @Override
               public void error(SAXParseException exception) throws SAXException {
                   throw exception;
               }

               @Override
               public void fatalError(SAXParseException exception) throws SAXException {
                   throw exception;
               }

               @Override
               public void warning(SAXParseException exception) throws SAXException {
                   // NOP
               }
           });
           return builder.parse(inputSource);
       } catch (Exception e) {
           throw new BuilderException("Error creating document instance.  Cause: " + e, e);
       }
 }
}
           

XML解析

XMLConfigBuilder

以及解析Mapper檔案的

XMLMapperBuilder

都繼承于

BaseBuilder

。他們對于XML檔案本身技術上的加載和解析都委托給了

XPathParser

,最終用的是jdk自帶的xml解析器而非第三方比如dom4j,底層使用了xpath方式進行節點解析。

new XPathParser(reader, true, props, new XMLMapperEntityResolver())

的參數含義分别是Reader,是否進行DTD 校驗,屬性配置,XML實體節點解析器。

entityResolver,跟Spring的XML标簽解析器一樣,有預設的解析器,也有自定義的,主要使用了政策模式,在這裡mybatis寫死為XMLMapperEntityResolver。

XMLMapperEntityResolver的定義如下:

package org.apache.ibatis.builder.xml;

import org.apache.ibatis.io.Resources;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;

/**
 * Offline entity resolver for the MyBatis DTDs.
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class XMLMapperEntityResolver implements EntityResolver {

  private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
  private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
  private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";

  private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";

    /**
     * Converts a public DTD into a local one. 将公共的DTD轉換為本地模式
     * @param publicId The public id that is what comes after "PUBLIC"
     * @param systemId The system id that is what comes after the public id.
     * @return The InputSource for the DTD
     * @throws org.xml.sax.SAXException If anything goes wrong
     */
  @Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
    try {
      if (systemId != null) {
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
        } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
          return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
        }
      }
      return null;
    } catch (Exception e) {
      throw new SAXException(e.toString());
    }
  }

 private InputSource getInputSource(String path, String publicId, String systemId) {
    InputSource source = null;
    if (path != null) {
      try {
        InputStream in = Resources.getResourceAsStream(path);
        source = new InputSource(in);
        source.setPublicId(publicId);
        source.setSystemId(systemId);
      } catch (IOException e) {
        // ignore, null is ok
      }
    }
    return source;
  }

}
           

mybatis解析的時候,引用了本地的DTD檔案,和本類在同一個package下,其中的ibatis-3-config.dtd應該主要是用于相容用途。在其中getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId)的調用裡面有兩個參數publicId(公共辨別符)和systemId(系統标示符),他們是XML 1.0規範的一部分,在mybatis中,systemId指定的是mybatis-3-config.dtd和ibatis-3-config.dtd

調用棧

build:64, SqlSessionFactoryBuilder (org.apache.ibatis.session)
-->build:77, SqlSessionFactoryBuilder (org.apache.ibatis.session)
	--><init>:89, XMLConfigBuilder (org.apache.ibatis.builder.xml)
		--><init>:127, XPathParser (org.apache.ibatis.parsing)
			-->createDocument:261, XPathParser (org.apache.ibatis.parsing)
				-->parse:339, DocumentBuilderImpl (com.sun.org.apache.xerces.internal.jaxp)
					-->parse:243, DOMParser (com.sun.org.apache.xerces.internal.parsers)
						-->parse:141, XMLParser (com.sun.org.apache.xerces.internal.parsers)
							-->parse:771, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
								-->parse:842, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
									-->scanDocument:505, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
										-->next:602, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
											-->next:959, XMLDocumentScannerImpl$PrologDriver (com.sun.org.apache.xerces.internal.impl)
												-->next:1045, XMLDocumentScannerImpl$DTDDriver (com.sun.org.apache.xerces.internal.impl)
													-->dispatch:1151, XMLDocumentScannerImpl$DTDDriver (com.sun.org.apache.xerces.internal.impl)
														-->resolveEntityAsPerStax:997, XMLEntityManager (com.sun.org.apache.xerces.internal.impl)
															-->resolveEntity:110, EntityResolverWrapper (com.sun.org.apache.xerces.internal.util)
																-->resolveEntity:58, XMLMapperEntityResolver (org.apache.ibatis.builder.xml)
           

UML關系圖

紅色的線是調用關系

mybatis_3源碼閱讀日記_容器的加載與初始化MyBatis整體架構MyBatis的主要成員SqlSessionFactory的建立

原圖

mybatis_3源碼閱讀日記_容器的加載與初始化MyBatis整體架構MyBatis的主要成員SqlSessionFactory的建立

這裡主要是根據mybatis自身建立一個文檔解析器(XMLMapperEntityResolver),然後調用parse将輸入input source解析為DOM XML文檔并傳回。

再得到XPathParser執行個體之後,就調用另一個使用XPathParser作為配置來源的重載構造函數了

構造函數代碼如下

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

           

其中調用了父類BaseBuilder的構造器目的是設定類型别名注冊器,以及類型處理器注冊器;并設定關鍵配置environment以及properties檔案。

Configuration配置生成

Configuration是個核心類xml配置檔案裡面的所有配置關鍵資訊都用這個類表示,而且幾乎所有的情況下都要依賴與Configuration這個類。

XMLConfigBuilder

建立完成之後,

SqlSessionFactoryBuild

調用

parser.parse()

建立

Configuration

,真正Configuration建構邏輯就在

XMLConfigBuilder.parse()

裡面

XMLConfigBuilder.parse

的代碼如下:

public Configuration parse() {
        // 判斷是否重複解析
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
         // 讀取配置檔案一級節點configuration,mybatis配置檔案解析的主流程
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
           

config檔案解析

XMLConfigBuilder.parseConfiguration

。,

parser.evalNode(“/configuration”)

傳回根節點的

org.apache.ibatis.parsing.XNode

表示,XNode裡面主要把關鍵的節點屬性和占位符變量結構化出來。然後調用

parseConfiguration

根據mybatis的主要配置進行解析。

主要代碼如下:

private void parseConfiguration(XNode root) {
        try {
            // properties 标簽,用來配置參數資訊,比如最常見的資料庫連接配接資訊
            propertiesElement(root.evalNode("properties"));
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            loadCustomVfs(settings);
            loadCustomLogImpl(settings);
            // 實體别名兩種方式:1.指定單個實體;2.指定包
            typeAliasesElement(root.evalNode("typeAliases"));
            pluginElement(root.evalNode("plugins"));
            // 插件
            objectFactoryElement(root.evalNode("objectFactory"));
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            settingsElement(settings);
            // 資料庫環境
            environmentsElement(root.evalNode("environments"));
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 資料庫類型和Java資料類型的轉換
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 這個是對資料庫增删改查的解析
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
           

所有的

root.evalNode

底層都是調用XML DOM的

evaluate()

方法,根據給定的節點表達式來計算指定的

XPath

表達式,并且傳回一個

XPathResult

對象,傳回類型在

Node.evalNode()

方法中均被強轉為Node對象。

  

  mybatis中所有環境配置、resultMap集合、sql語句集合、插件清單、緩存、加載的xml清單、類型别名、類型處理器等全部都維護在Configuration中。Configuration中包含了一個内部靜态類StrictMap,它繼承于HashMap,對HashMap的裝飾在于增加了put時防重複的處理,get時取不到值時候的異常處理,這樣核心應用層就不需要額外關心各種對象異常處理,簡化應用層邏輯。

  從設計上來說,我們可以說Configuration并不是一個thin類(也就是僅包含了屬性以及getter/setter),而是一個rich類,它對部分邏輯進行了封裝便于使用者直接使用,而不是讓使用者各自散落處理,比如addResultMap方法和getMappedStatement方法等等

  Configuration類的代碼我就不在此處粘貼,有興趣的可以通路https://mybatis.org/mybatis-3/zh/configuration.html#settings

所有我們在mybatis-config和mapper檔案中使用的類似int/string/JDBC/POOLED等字面常量最終解析為具體的java類型都是在typeAliasRegistry構造器和Configuration構造器執行期間初始化的。這個可以在Configuration構造器和

protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry()

屬性中看到

繼續閱讀