您当前的位置: 首页 >  sql

white camel

暂无认证

  • 0浏览

    0关注

    442博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

MyBatis运行原理(一) : 核心组件、SqlSessionFactory的构建过程(源码分析)

white camel 发布时间:2021-04-22 11:40:54 ,浏览量:0

MyBatis的工作原理 MyBatis运行原理图

在这里插入图片描述

  • SqISessionFactoryBuilder (构建器): 根据配置信息或Java代码来构建 SqlSessionFactory 对象。作用: 创建SqlSessionFactory对象。(并创建出Configuration全局配置文件对象)
  • SqlSessionFactory (会话工厂) :好比是DataSource(创建连接的数据源) ,线程安全的,在应用运行期间不要重复创建多次,建议使用单例模式。作用: 创建SqlSession对象
  • SqlSession (会话) :好比是Connection ,线程不安全的,每次使用开启新的SqlSession对象,使用完毕正常关闭,默认使用DefaultSqlSession。提供操作数据库的增删改查方法,可以调用操作方法,也可以操作Mapper组件。

原理图: 在这里插入图片描述 更具体的底层原理图: 在这里插入图片描述 在这里插入图片描述

涉及的对象:

  • Configuration : MyBatis 全局配置对象,封装所有配置信息
  • MappedStatement :映射语句对象,每一个该对象都封装了一个< insert|update|delete|select>节点的详细信息,包括标签的所有可写属性。
    • SqlSource : SQL源(是MappedStatement的属性), 根据用户传入的参数生成SQL语句,并封装到BoundSql中
    • BoundSql : SQL绑定, 封装SQL语句和对应的参数信息进行绑定
  • Executor :执行器,是MyBatis调度的核心, 负责SQL语句的具体操作和查询缓存的维护
  • StatementHandler :语句处理器,封装了JDBC的DML、DQL 操作、参数设置
  • ParameterHandler :参数处理器,把用户传入参数转换为JDBC需要的参数值
  • ResultSetHandler :结果集处理器,把结果集中的数据封装到List集合
  • TypeHandler :类型转换器, Java类型和JDBC类型的相互转换

在这里插入图片描述 在这里插入图片描述

二、SqlSessionFactory的初始化过程 (源码分析)

在这里插入图片描述 1、进入到bulid方法

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 根据mybatis-config.xml包装的流, 创建了一个解析器parser;该对象是XPathParser, 
      // 基于Dom4j的XPath的方式来解析XML
      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.
      }
    }
  }

2、进入XMLConfigBuilder类的parse()方法: 用来解析mybatis-config.xml的方法

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 通过parser解析器来解析mybatis-config.xml中的configuration节点,拿到该根节点
    parseConfiguration(parser.evalNode("/configuration"));
    // 返回配置对象
    return configuration;
  }

3、进入parseConfiguration方法

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      // 根据configuration根节点来拿到它下面的节点, 这里获取settings节点,
      // 封装到一个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);
    }
  }

4.1、进入到settingsElement方法中: 将保存到properties中的setting配置内容, 设置给Configuration对象

  private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    // 省略...
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }

4.2、进如到mapperElement方法, 解析mappers节点

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          // 因为是基于resource的方式绑定mapper.xml, 所以进入该分支
          // resourse: com/sunny/mapper/UserMapper.xml
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url"); // 没用,为null
          String mapperClass = child.getStringAttribute("class"); // 没用,为null
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            // 同加载mybatis-config.xml,来加载mapper.xml
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 创建解析器
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            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.");
          }
        }
      }
    }
  }

5、进入到mapperPaser.parse方法: 用来解析mapper.xml的方法, 将解析mapper.xml的标签都保存到全局配置文件中

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 解析并拿到mapper标签
      configurationElement(parser.evalNode("/mapper"));
      // 将解析mapper.xml的标签都保存到全局配置文件中
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

6、进入XMLMapperBuilder类的configurationElement方法

private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      // 通过mapper根节点来拿到其他子节点
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      // 以crud为例来分析
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

7、进入buildStatementFromContext方法

private void buildStatementFromContext(List list) {
	// configuration用来保存所有的mybatis的配置信息
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    // 进入该方法
    buildStatementFromContext(list, null);
  }
  
private void buildStatementFromContext(List list, String requiredDatabaseId) {
  for (XNode context : list) {
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

在这里插入图片描述 8、XMLStatementBuilder的parseStatementNode方法: 用来解析select | insert | update | delete标签的方法

// 拿到select | insert | update | delete标签能写的所有属性
public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
	// 省略.....
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

	// 将获取到的所有crud的标签属性通过下面方法构造出来
	// public MappedStatement addMappedStatement(){} 该方法返回一个 MappedStatement
	// 也就说明每个增删改查标签的所有信息, 都封装成了MappedStatement对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

9、进入到addMappedStatement方法, 根据select | insert | update | delete标签构建出MappedStatement对象

  public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class parameterType,
      String resultMap,
      Class resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }
	
	// 根据select | insert | update | delete标签构建出MappedStatement对象
    MappedStatement statement = statementBuilder.build();
    // 将statement对象扔保存到configuration对象中
    configuration.addMappedStatement(statement);
    return statement;
  }

每一个MappedStatement保存的信息如下图 在这里插入图片描述 10、回到XMLConfigBuilder类的parse()方法, 返回configuration, 此时该对象就保存了mybatis-config.xml和mapper.xml以及crud标签的详细信息, 如下图: 在这里插入图片描述 configuration中的mappedStatements来保存了crud标签信息 在这里插入图片描述 在这里插入图片描述 11、最终返回了一个DefaultSqlSession对象, 该对象包含了全局配置信息Configuration 在这里插入图片描述 12、最终new SqlSessionFactoryBuilder().build(in)返回了SqlSessionFactory --> DefaultSqlSessionFactory对象 在这里插入图片描述 上面1-12步时序图

  • 总结: new SqlSessionFactoryBuilder().build(in)流程: 把配置文件的信息解析并保存在Configuration对象中, 返回包含了Configurationd对DefaultSqlSessionFactory对象 在这里插入图片描述
关注
打赏
1661428283
查看更多评论
立即登录/注册

微信扫码登录

0.0450s