MyBatis源码解析(二):初始化SqlSessionFactory

一,SqlSessionFactory初始化流程图

二,初始化步骤

    1,获取配置文件 mybatis-config.xml,并初始化为 Document

        * SqlSessionFactory.build()

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 初始化XMLConfigBuilder, 内部通过XPathParser解析inputStream为Document
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // parser.parse() : 解析Document配置文件
      // build(parser.parse()) : 构架生成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.
      }
    }
  }

        * 初始化 XMLConfigBuilder 时,会关联初始化 XPathParser

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

        * XPathParser 内部,解析 InputStream 配置文件流,生成 Document 对象,Document 对象被 XPathParser 持有,而 XPathParser 被 XMLConfigBuilder 持有

public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    // 解析配置文件流, 生成Document文件
    this.document = createDocument(new InputSource(inputStream));
  }

    2,通过 XMLConfigBuilder 解析配置文件中的每一个节点

        * MyBatis 核心配置节点定义

        * 从第一步可知,XML配置文件解析所生成的 Document 对象,已经被 XMLConfigBuilder 内部属性持有,现在进行解析

        * parser.parse(),从上图可知,MyBatis 核心配置文件,第一层标签为 <configuration>,则文件解析从该标签开始

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 从mybatis-config.xml配置文件的根节点<configuration>开始, 解析配置文件
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

        * parser.evalNode(String) 方法内部会解析出当前节点所有子节点对象 XNode,

        * parseConfiguration(XNode)方法继续对节点所属子节点依次解析,该方法中所解析的属性基本与上图中核心配置标签一致,下面会对 <setting>标签、<environments>标签和<mappers>标签进行分析

private void parseConfiguration(XNode root) {
    // 解析mybatis-config.xml文件中各个元素节点
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(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
      // 解析<environments>, 获取数据库连接信息
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析mappers节点
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

    3,解析Setting节点,获取配置信息

        * Setting节点解析方法

private void settingsElement(Properties props) throws Exception {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
    configuration.setDefaultEnumTypeHandler(typeHandler);
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    @SuppressWarnings("unchecked")
    Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }

        * Setting节点解析会对 <Setting>标签中需要处理的对象进行初始化,如果没有该属性的配置信息则会初始化初值,比如后续初始化的 SimpleExecutorType 和 CacheingExecutor

        * Setting 节点所包含的属性及默认值可从官网查看 :http://www.mybatis.org/mybatis-3/zh/configuration.html

    4,解析 Environment 节点,获取数据库配置信息

        * 节点解析方法:environmentsElement(),构建 Environment 数据后,添加到 Configuration 对象中

  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"));
          // 数据库配置信息解析
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          // 添加环境信息 + 数据库配置信息到Configuration
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

        * 数据库连接配置信息解析 dataSourceElement()

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
      // 获取数据库连接类型
      String type = context.getStringAttribute("type");
      // 获取数据库连接基础配置信息
      Properties props = context.getChildrenAsProperties();
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }

        * 数据库连接信息 Properties 获取,

public Properties getChildrenAsProperties() {
    Properties properties = new Properties();
    for (XNode child : getChildren()) {
      String name = child.getStringAttribute("name");
      String value = child.getStringAttribute("value");
      if (name != null && value != null) {
        properties.setProperty(name, value);
      }
    }
    return properties;
  }

    5,解析 Mapper 节点

        * Mapper映射文件节点定义,   http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html

        * 节点解析方法:mapperElement(),并以常用配置参数 Resource 为例进行后续解析

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 解析<package name="org.mybatis.builder"/>形式
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          // 解析<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>形式
          String resource = child.getStringAttribute("resource");
          // 解析<mapper url="file:///var/mappers/AuthorMapper.xml"/>形式
          String url = child.getStringAttribute("url");
          // 解析<mapper class="org.mybatis.builder.AuthorMapper"/>形式
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 内部通过XPathParser生成当前resource指向的xml文件的document
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 解析当前mapper.xml文件
            mapperParser.parse();
          } 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();
          } 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.");
          }
        }
      }
    }
  }

        * 构建 XMLMapperBuilder,并在内部通过 XPathParser 生成mapper.xml映射文件的Document

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

        * 通过 XMLMapperBuilder 对象解析Document

public void parse() {
    // 判断当前mapper.xml是否已经被解析
    // 没有被解析, 直接解析
    if (!configuration.isResourceLoaded(resource)) {
      // 配置解析mapper大标签内每一个组件
      // mapper大标签标识一个mapper.xml文件
      // 此处解析完成后, 直接添加到Configuration中
      configurationElement(parser.evalNode("/mapper"));
      // 添加当前已经被解析的mapper.xml文件到集合中, 进行是否已经被解析判断
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
    // 解析其余未被解析的元素
    // 通过部分操作, 可在不重启的情况下触发Mapper二次解析(个人理解)
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

    6,加载 Mapper 节点到 Configuration 和 MapperRegistery 中

        * Mapper 标签解析完成后,添加当前 Mapper 到Configuration

private void bindMapperForNamespace() {
    // 获取当前Mapper的名称空间
    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
          // 添加当前名称空间和Mapper对象到configuration
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }

        * Configuration 类委托 MapperRegistry 进行Mapper实例添加

public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

        * MapperRegistry 最终添加 Mapper 实例到 Map 集合中,后续通过SqlSession.getMapper()会从该集合中获取 Mapper实例

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

    7,MyBatis 核心配置文件所关联需要解析的数据解析完成后,直接初始化 DefaultSqlSessionFactory,从方法可以看出,DefaultSqlSessionFactory默认持有 Configuration 引用

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值