MyBatis之啟動分析(一)

  • 2019 年 10 月 29 日
  • 筆記

ytao

前言

MyBatis 作為目前最常用的持久層框架之一,分析其源碼,對我們的使用過程中可更好的運用它。本系列基於mybatis-3.4.6進行分析。
MyBatis 的初始化工作就是解析主配置文件,映射配置文件以及註解資訊。然後保存在org.apache.ibatis.session.Configuration,供後期執行數據請求的相關調用。
Configuration 里有大量配置資訊,在後面每涉及到一個相關配置,會進行詳細的分析。

啟動

    public static void main(String[] args) throws IOException {          // 獲取配置文件          Reader reader = Resources.getResourceAsReader("mybatis-config.xml");          // 通過 SqlSessionFactoryBuilder 構建 sqlSession 工廠          SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);          // 獲取 sqlSession 實例          SqlSession sqlSession = sqlSessionFactory.openSession();            reader.close();          sqlSession.close();      }

分析

SqlSessionFactoryBuilder 類

SqlSessionFactoryBuilder 的build()是Mybatis啟動的初始化入口,使用builder模式載入配置文件。
通過查看該類,使用方法重載,有以下9個方法:
SqlSessionFactoryBuilde類中的方法

方法重載最終實現處理的方法源碼如下:

    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {          try {            // 實例化 XMLConfigBuilder,用於讀取配置文件資訊            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);            // 解析配置資訊,保存到 Configuration            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.            }          }        }
  • environment 是指定載入環境,默認值為 null。
  • properties 是屬性配置文件,默認值為 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.            }          }        }

實例化 XMLConfigBuilder 類

通過 SqlSessionFactoryBuilder 中 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties), 分析 XMLConfigBuilder實例化過程。
該類中有四個變數:

    private boolean parsed;      private final XPathParser parser;      private String environment;      private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
  • parsed 是否解析,一次解析即可。用於標誌配置文件只解析一次,true為已解析過。
  • parser 解析配置的解析器
  • environment 載入環境,即 SqlSessionFactoryBuilder 中的 environment
  • localReflectorFactory 用於創建和快取Reflector對象,一個類對應一個Reflector。因為參數處理、結果映射等操作時,會涉及大量的反射操作。DefaultReflectorFactory實現類比較簡單,這裡不再進行講解。

XMLConfigBuilder構建函數實現:

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

實例化 XPathParser 對象

首先實例化 XPathParser 對象,裡面定義了5個變數:

    private final Document document;      private boolean validation;      private EntityResolver entityResolver;      private Properties variables;      private XPath xpath;
  • document 保存document對象
  • validation xml解析時是否驗證文檔
  • entityResolver 載入dtd文件
  • variables 配置文件定義的值
  • xpath Xpath對象,用於對XML文件節點的操作

XPathParser 對象構造函數有:
XPathParser構造方法
函數裡面都處理了兩件事:

    public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {          commonConstructor(validation, variables, entityResolver);          this.document = createDocument(new InputSource(reader));      }
  1. 初始化賦值,和創建XPath對象,用於對XML文件節點的操作。
    private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {          this.validation = validation;          this.entityResolver = entityResolver;          this.variables = variables;          // 創建Xpath對象,用於對XML文件節點的操作          XPathFactory factory = XPathFactory.newInstance();          this.xpath = factory.newXPath();      }
  1. 創建Document對象並賦值到document變數, 這裡屬於Document創建的操作,不再詳細講述,不懂可以點擊這裡查看API
    private Document createDocument(InputSource inputSource) {          // important: this must only be called AFTER common constructor          try {            // 實例化 DocumentBuilderFactory 對象,用於創建 DocumentBuilder 對象            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();            // 是否校驗文檔            factory.setValidating(validation);            // 設置 DocumentBuilderFactory 的配置            factory.setNamespaceAware(false);            factory.setIgnoringComments(true);            factory.setIgnoringElementContentWhitespace(false);            factory.setCoalescing(false);            factory.setExpandEntityReferences(true);            // 創建 DocumentBuilder            DocumentBuilder builder = factory.newDocumentBuilder();            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 {              }            });            // 載入文件            return builder.parse(inputSource);          } catch (Exception e) {            throw new BuilderException("Error creating document instance.  Cause: " + e, e);          }      }

XMLConfigBuilder構造函數賦值

    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;      }
  1. 初始化父類BaseBuilder的值。
  2. 將外部值賦值給對象。
  3. 將實例化的XPathParser賦值給parser

最後返回XMLConfigBuilder對象。

解析 XMLConfigBuilder 對象

通過 XMLConfigBuilder.parse() 解析配置資訊,保存至Configuration。解析詳解在後面文章中進行分析。

    public Configuration parse() {          // 是否解析過配置文件          if (parsed) {            throw new BuilderException("Each XMLConfigBuilder can only be used once.");          }          // 標誌解析過,定義為 true          parsed = true;          // 解析 configuration 節點中的資訊          parseConfiguration(parser.evalNode("/configuration"));          return configuration;      }

創建 SqlSessionFactory

DefaultSqlSessionFactory實現了SqlSessionFactory介面。
通過上面解析得到的Configuration,調用SqlSessionFactoryBuilder.build(Configuration config)創建一個 DefaultSqlSessionFactory

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

實例化DefaultSqlSessionFactory的過程,就是將Configuration傳遞給DefaultSqlSessionFactory成員變數configuration

    public DefaultSqlSessionFactory(Configuration configuration) {          this.configuration = configuration;      }

創建 SqlSession

通過調用SqlSessionFactory.openSession()創建SqlSession

    public interface SqlSessionFactory {        // 默認創建        SqlSession openSession();          SqlSession openSession(boolean autoCommit);        SqlSession openSession(Connection connection);        SqlSession openSession(TransactionIsolationLevel level);          SqlSession openSession(ExecutorType execType);        SqlSession openSession(ExecutorType execType, boolean autoCommit);        SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);        SqlSession openSession(ExecutorType execType, Connection connection);          Configuration getConfiguration();        }
  • autoCommit 是否自動提交事務,
  • level 事務隔離級別(共5個級別), 可查看相關源碼
  • connection 連接
  • execType 執行器的類型:SIMPLE(不做特殊處理), REUSE(復用預處理語句), BATCH(會批量執行)

因為上面DefaultSqlSessionFactory實現了SqlSessionFactory介面,所以進入到DefaultSqlSessionFactory查看openSession()

    public SqlSession openSession() {          return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);      }

openSession()方法最終實現程式碼如下:

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {          Transaction tx = null;          try {            // 獲取configuration中的載入環境            final Environment environment = configuration.getEnvironment();            // 獲取事務工廠            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);            // 創建一個事務            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);            // 生成一個處理器,事務保存在處理器 BaseExecutor 中            final Executor executor = configuration.newExecutor(tx, execType);            // 實例化一個 DefaultSqlSession,DefaultSqlSession實現了SqlSession介面            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();          }      }

生成處理器Configuration.newExecutor(Transaction transaction, ExecutorType executorType)

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {          // 默認為 ExecutorType.SIMPLE          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;      }

ExecutorType.SIMPLE為例, BatchExecutor, ReuseExecutor同理:
SimpleExecutor父類圖
至此,mybatis的啟動流程大致簡單的介紹到這裡,對mybatis的啟動初始化有個大致了解。接下將會針對單獨模組進行詳細分析。

個人部落格: https://ytao.top
我的公眾號 ytao
我的公眾號