mybatis學習_SqlSessionFactory構建過程

使用mybatis功能前,我們先得創建一個 SqlSessionFactory 對象,創建過程如下。我們實例化了一個 SqlSessionFactoryBuilder對象,然後讓其加載mybatis的配置文件,構建出一個SqlSessionFactory 對象。

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

1. 相關組件

介紹SqlSessionFactory 對象的構建過程前,我們先熟悉一下參與這個構建過程的相關組件。以便後面我們更好的分析源碼

1.1 Configuration

見名知意,這是 mybaits提供的一個配置類。mybatis所有的配置信息,以及某些配置的默認值都封裝在此類中。在構建SqlSessionFactory對象時,mybatis會將傳入的xml配置文件的配置信息映射到configuration配置類中。並且,configuration還爲我們的別名註冊器註冊了默認的別名。

 public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    ......

1.2 SqlSessionFactory 和 SqlSessionFactoryBuilder

SqlSessionFactory 是一個構建SqlSession的工廠。SqlSession可以被認爲是與數據庫一次會話。SqlSessionFactory提供了非常多的重載的 openSession 方法,用來構建SqlSession,以及一個獲取配置類getConfiguration方法,用來動態的獲取配置信息或更改配置信息。其實現類DefaultSqlSessionFactory內就維護了一個final的Configuration對象。我們通過通常所說的SqlSessionFactory 就是DefaultSqlSessionFactory。

同理,SqlSessionFactoryBuilder 就是用來構建SqlSessionFactory的建造者,其中維護了許多重載的build方法,用於構建SqlSessionFactory。最後所有的build方法又指向了同一個重載的 build(Configuration config) 方法,用於最終的創建SqlSessionFactory對象。

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

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

  @Override
  public SqlSession openSession() {......}
  ......
}


public class SqlSessionFactoryBuilder {

    ......

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

1.3 XPathParser 和 XMLConfigBuilder

XPathParser 是一個mybatis內置的xml文件解析器,其中維護了一個Document對象。XPathParser 在實例化時,就會將傳入的xml文件信息轉換成一個Document對象,並提供了一系列的解析Document對象的方法。

XMLConfigBuilder 就是用來生成我們的配置類的建造者,其中維護一個XPathParser 對象,其父類BaseBuilder維護一個Configuration 對象。XMLConfigBuilder利用XPathParser 解析xml文件中的各個節點,最後將解析到的信息封裝在 Configuration 對象中。其核心方法 parse方法,返回一個 Configuration 對象。

public class XMLConfigBuilder extends BaseBuilder {
  private final XPathParser parser;
  ......
  public Configuration parse() {......}
  ......
}

public abstract class BaseBuilder {
  protected final Configuration configuration;
  ......
}

public class XPathParser {
  private final Document document;
  ......
}

 

2. 構建過程

通過對上面相關組件的瞭解,SqlSessionFactory構建的大致流程也就比較清晰了。

  1. 實例化一個 SqlSessionFactoryBuilder對象,調用其build方法,並將xml信息作爲參數傳入,獲取到SqlSessionFactory。
  2. SqlSessionFactoryBuilder 利用xml信息作爲參數創建了一個XmlConfigBuilder對象,並調用其parse方法生成一個Configuration對象。
  3.  XmlConfigBuilder 的 parse 方法構建並返回了 Configuration 對象。
  4. SqlSessionFactoryBuilder拿到 Configuration後,實例化了一個 DefaultSqlSessionFactory返回。

不難發現,SqlSessionFactory構建過程的核心就是 Configuration 對象的構建,而這個過程的具體實現就在XmlConfigBuilder的parse方法中。

public Configuration parse() {
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    //標記爲已解析狀態
    parsed = true;
    //這裏的parser就是XmlConfigBuilder維護的XPathParser對象
    //這裏拿到了xml的configuration節點下的所有內容,並交給parseConfiguration方法處理
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

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

parseConfiguration方法調用了一系列的方法去解析xml配置文件中的各個節點,這裏我們重點看看解析mappers節點的 mapperElement方法。

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      //遍歷 mappers節點下的子節點
      for (XNode child : parent.getChildren()) {
        //解析 package 子節點
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          //解析<mapper resource="***.xml"/>形式的配置
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } 
          //解析<mapper url="***.xml"/>形式的配置
          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();
          } 
          //解析<mapper mapperClass ="***"/>形式的配置
          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.");
          }
        }
      }
    }
  }

mappers節點的解析可以分成兩個部分來看

1. 如果遍歷到package子節點,則將該包下所有的Class註冊到Configuration的mapperRegistry中。若是mapper子節點的class屬性,則將指定的class註冊到Configuration的mapperRegistry中。mapperRegistry會根據接口class的全限定名找到對應的XML配置文件進行解析。

2. 如果遍歷到mapper子節點的resource或者url屬性,就直接是實例化一個 XmlMapperBuilder對象對xml進行解析。

xml映射文件的解析過程這裏不詳細敘述。XmlMapperBuilder最終會將映射文件解析成一個MappedStatement對象,並將其註冊到Configuration對象中。

當所有的節點都解析完成後SqlSessionFactory的構建過程也就基本完成了。

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