mybatis隨筆一之SqlSessionFactoryBuilder


SqlSessionFactoryBuilder是構建sqlSessionFactory的入口類

從該類的方法可知,它是通過不同的入參來構造SqlSessionFactory,除了最后一個configuration入參方法外,其余方法最終都調用如下方法
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類的實例
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
在這個初始化過程中,首先創建了XMLMapperEntityResolver類的實例,這個類顧名思義是個實體mapper文件實體解析器,里面有個map將mybatis的xml文件與對應的解析文件關系保存起來,初始化后再實例化XPathParser
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(inputStream));
  }
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }
commonConstructor比較簡單,就是給一些類變量賦值,並且初始化了一個xpath對象,這里使用了工廠設計模式。
new InputSource(inputStream)就是將輸入流賦予自己內部的一個變量byteStream。
createDocument(new InputSource(inputStream))這里面主要做了這幾件事通過DocumentBuilderFactory生成DocumentBuilder,並將entityResolver賦給它的屬性字段同時定義了一個處理異常的內部類,
然后通過builder.parse(inputSource)將輸入流解析成一個document對象。
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;
  }
new Configuration()主要是在構造方法里面注冊了別名
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
registerAlias是TypeAliasRegistry類的方法,將別名與類的關系保存在了內部的private final Map<String, Class<?>> TYPE_ALIASES
public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }
super(new Configuration())就是將相應的變量保存在父類屬性上,方便其它子類使用。
ErrorContext.instance()方法內部采用的threadlocal方式,每個線程都持有一個ErrorContext對象resource("SQL Mapper Configuration")就是一個簡單的賦值給resource變量方法。
至此一個XMLConfigBuilder對象就創建出來了,然后就進入了build(parser.parse())。
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
parser.evalNode("/configuration")在初始化過程中已經將mybatis_config.xml文件解析成document對象,這里使用xpath解析工具將configuration節點解析成一個XNode對象。

private void parseConfiguration(XNode root) {
    try {
      Properties settings = settingsAsPropertiess(root.evalNode("settings"));
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectionFactoryElement(root.evalNode("reflectionFactory"));
      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);
    }
  }
該方法一看便知是掃描configuration節點下的諸如properties、typeAliases節點做相應的處理。稍微分析其中幾個方法
propertiesElement(root.evalNode("properties"))是讀取properties節點下的property子元素以及resource指向的資源文件路徑,將兩者合並為一個properties並保存到內部的vars變量。
typeAliasesElement(root.evalNode("typeAliases"))讀取子元素時首先判斷是否package,是的話掃描包下的非接口、匿名、內部類注冊別名。
pluginElement(root.evalNode("plugins"))將插件加到插件鏈上。
重點提一下最后的mapperElement方法,這個方法內首先判斷子元素是不是package,若是<mapper resource="mapper/demo.xml"/>這種形式的。其中最重要的是下面兩行代碼
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
構建XMLMapperBuilder對象前面已經分析過了,重點分析parse方法
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }
private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }
該方法獲取mapper文件的namespace來實例化mapper接口,這也就解釋了為什么namespace要和mapper接口全路徑一致。
其中值得注意的是configuration.addMapper(boundType)方法,該方法調用了mapperRegistry.addMapper(type)方法。
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
MapperProxyFactory是個代理接口方法工廠,后面會使用到它,就是通過它來給沒有實現類的mapper接口代理。
重點看其中的parser.parse()方法
public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }
先得到接口里的methods,然后遍歷解析方法,查看該方法有沒有注釋sql語句有的話拼接這些語句,因為現在流行使用xml文件配置方式更加靈活的處理sql語句,因此這里跳過。
parsePendingMethods方法是有些insert或者update的select語句引入了sql片段,但是sql片段還沒解析到,因此先將這些方法pend,后面每有新的mapper解析時都會嘗試解析完這些pend方法。
同理parsePendingResultMaps(),parsePendingChacheRefs(),parsePendingStatements()三個方法也類似。
現在configuration已經基本解析完成,然后調用
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
返回一個默認的SqlSessionFactory實現,其內部持有一個Configuration變量。

至此SqlSessionFactoryBuilder創建DefaultSqlSessionFactory的過程完成。
 
        
 




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM