天天看點

Mybatis 源碼分析(二) | MyBatis 配置檔案解析過程

前言

如何調試源碼請參考這一篇文章《如何調試Mybatis源碼 》。

注意:如果你還沒有調試過mybatis,那麼不建議你往下閱讀,因為你會看的稀裡糊塗-。-

本篇我們就一塊Debug源碼,看看Mybatis到底是如何讀取到 mybatis-config.xml 檔案中的一系列參數的呢?

首先我們看張圖:這是我們JDBC時對資料庫的一系列操作,先擷取資料源中一系列的參數,然後執行SQL語句,最後對資料庫進行操作,擷取到結果。

本章節就先說說第一部分,mybatis如何擷取到資料源。

Mybatis 源碼分析(二) | MyBatis 配置檔案解析過程

Mybatis如何擷取資料源

在 MyBatis 初始化過程中,會加載 mybatis-config.xml 配置檔案、映射配置檔案以及 Mapper 接口中的注解資訊,解析後的配置資訊會形成相應的對象并儲存到 Configuration 對象中。利用該 Configuration 對象建立 SqlSessionFactory對象。待 MyBatis 初始化之後,開發人員可以通過初始化得到 SqlSessionFactory 建立 SqlSession 對象并完成資料庫操作。
  • 對應

    builder

    子產品,為配置解析過程。主要的xml檔案的解析都在這個子產品下進行。
    Mybatis 源碼分析(二) | MyBatis 配置檔案解析過程
    第一步要做的事情就是根據配置檔案建構SqlSessionFactory對象。,使用

    Resources

    工具類加載了

    mybatis-config.xml

    檔案,得到輸入流,然後通過

    SqlSessionFactoryBuilder

    build()

    方法,建構出

    sqlSessionFactory

    對象。
public static void main(String[] args) throws IOException {
        // 擷取配置檔案
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 建構SqlSessionFactory對象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            User user = sqlSession.selectOne("com.mybatis.read.UserDao.selectUser", 1);
        } finally {
            sqlSession.close();
        }
    }
           

SqlSessionFactoryBuilder

可以看出這裡調用了

XMLConfigBuilder

對象的

parse()

方法,對 xml 檔案進行了解析,那麼如何解析的呢?進入

XMLConfigBuilder

類中一探究竟吧。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // 建立 XMLConfigBuilder 對象
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 執行 XML 解析
            // DefaultSqlSessionFactory對象
            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);
    }
           

XMLConfigBuilder

  1. Configuration 這個對象貫穿了整個配置檔案初始化的過程。
  2. 對xml檔案中的标簽做了解析,從源碼中可以看出幾個關鍵的标簽有

    configuration

    properties

    environments

    dataSource

    mapper

  3. 通過

    configuration.setVariables()

    configuration.setEnvironment()

    将配置檔案中的

    properties

    environments

    下的參數放到了

    Configuration

    對象中供全局使用
public Configuration parse() {
    // 隻加載一次xml檔案,否則抛出異常
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 解析xml檔案中configuration标簽
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  /**
   * 功能描述: 解析 configuration 标簽下的所有屬性配置
   * @date 2020/2/27
   */
  private void parseConfiguration(XNode root) {
    try {
      // 讀取mybatis-config.xml中的properties,加載config.properties檔案中的參數
      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);
      // 加載environments節點
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 加載mapper檔案
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
           

解析 properties 配置

properties 的解析過程比較簡單,由代碼中可以看出,屬性配置是從

resource

或者

url

中擷取的,即檔案系統或者網絡位址。

項目中配置的是

resource

<properties resource = "config.properties"/>

。将

config.properties

檔案中的内容讀取出來,然後設定到

XPathParser

Configuration

對象中。

config.properties檔案内容如下:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=root
           
/***
   * 功能描述:解析properties節點,擷取url,driver,username,password
   * @date 2020/2/27
   */
  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      // properties 标簽裡的屬性,隻能設定resource或者url其中一個,否則抛出異常
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      // 加載了resource的檔案,把裡面的參數放到defaults中
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }
           

defaults

中的參數如下:

Mybatis 源碼分析(二) | MyBatis 配置檔案解析過程

解析 environments 配置

在 MyBatis 中,事務管理器和資料源是配置在 environments 中的。它們的配置大緻如下:

<environments default="development">
     <environment id="development">
         <transactionManager type="JDBC"/>
         <dataSource type="POOLED">
             <property name="driver" value="${driver}"/>
             <property name="url" value="${url}"/>
             <property name="username" value="${username}"/>
             <property name="password" value="${password}"/>
         </dataSource>
     </environment>
</environments>
           

解析

environments

标簽,之後解析了内部的

dataSource

标簽。

/***
   * 功能描述:解析 environments 标簽
   * @date 2020/2/27
   */
  private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          // 解析environment下的dataSource标簽
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

  /**
   * 功能描述:解析 dataSource 标簽
   * @date 2020/2/27
   */
  private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }
           

dataSource

中的參數如下:

Mybatis 源碼分析(二) | MyBatis 配置檔案解析過程

以上代碼的流程簡單的花了一個圖,可以大緻明白是怎樣的一個流程:

Mybatis 源碼分析(二) | MyBatis 配置檔案解析過程

小結

至此,我們已經可以看出Mybatis是如何從配置檔案中加載出資料源了。

在解析

Configuration

标簽時我們隻關注了和資料源參數相關的

properties

environments

标簽,還有一些同樣重要的标簽沒有展開說明,有興趣的同學同樣可以自行debug到代碼中,看看其他參數是設定,并産生作用的。

下一節說一說 Mapper 映射配置檔案 是如何加載的。

繼續閱讀