mybatis 启动流程分析(原理)

目标:

从整体上了解mybaits框架,并通过debug过程分析启动过程中涉及到的一些关键节点。如果需要了解sql执行过程的整体情况,可以了解下我的另外一篇mybatis SQL执行流程分析(原理)

mybaits 相比原生jdbc 有了哪些改进

如果您不是很明白为啥不直接用jdbc 而是要用mybatis,建议您阅读这篇文章 看看原始的jdbc访问数据库方式,和使用mybatis框架访问数据库方式的差异。我们会发现框架给人带来了很大的方便。主要是下面几点

  • 连接获取和释放
  • SQL统一存取
  • 传入参数映射和动态SQL
  • 结果映射和结果缓存

MyBatis框架整体设计

在这里插入图片描述

接口层

负责和应用层之间做交互,提供api接口给到用户调用,执行常见的增删改查,我们常见的mapper接口实际就是该层的。

数据处理层

  • 通过传入参数,构建动态SQL语句;
  • SQL语句的执行以及封装查询结果集成List

框架支撑层

  • 事务管理机制
  • 连接池管理机制
  • 缓存机制
  • SQL语句的配置方式

主要构件及其相互关系

  • SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;
  • Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;
  • StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
  • ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数;
  • ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
  • TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换;
  • MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装;
  • SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回;
  • BoundSql:表示动态生成的SQL语句以及相应的参数信息;
  • Configuration:MyBatis所有的配置信息都维持在Configuration对象之中;

在这里插入图片描述

MyBatis初始化机制

初始化入口

我们都知道使用mybatis的时候回有一个配置文件mybatis-config.xml ,mybait框架的初始化也是从这个配置文件开始的。
如果你是用的spring的话,在spring的配置文件中往往会有初始化sqlSessionFactory的过程。而在实例化sqlSessionFactory的过程中就会解析我们前面提到的配置文件(mybatis-config.xml),也是我们需要重点分析的部分。
在这里插入图片描述

核心配置文件的解析


  //解析配置
  private void parseConfiguration(XNode root) {
    try {
      //分步骤解析
      //1.properties
      propertiesElement(root.evalNode("properties"));
      //2.类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //3.插件
      pluginElement(root.evalNode("plugins"));
      //4.对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      //5.对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.设置
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //7.环境
      environmentsElement(root.evalNode("environments"));
      //8.databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9.类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10.映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

主要是把配置文件里面的一个个节点分别解析到Configuration对象内保存。我们重点分析第10点,映射器的解析。

映射器的解析

映射器就是我们应用内写的一个个mapper 以及他的xml文件。mybaits 会通过XMLMapperBuilder对象来对这类xml文件解析。类似的如下图,包括三个builder对象,xmlconfigbuilder 是用于前面mybatis-config.xml 文件解析,用户定义的这类包含了sql的xml文件解析则是通过
XMLMapperBuilder实现,statementbuilder 的用法我们后文会提到。
在这里插入图片描述
解析mapper文件和核心方法入口

 //解析mapper文件和核心方法入口
  public void parse() {
    //如果没有加载过再加载,防止重复加载
    if (!configuration.isResourceLoaded(resource)) {
      //配置mapper
      configurationElement(parser.evalNode("/mapper"));
      //标记一下,已经加载过了
      configuration.addLoadedResource(resource);
      //绑定映射器到namespace
      bindMapperForNamespace();
    }
    //还有没解析完的东东这里接着解析  
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }
//配置mapper元素 
//	<mapper namespace="org.mybatis.example.BlogMapper">
//	  <select id="selectBlog" parameterType="int" resultType="Blog">
//	    select * from Blog where id = #{id}
//	  </select>
//	</mapper>
  private void configurationElement(XNode context) {
    try {
      //1.配置namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      //2.配置cache-ref
      cacheRefElement(context.evalNode("cache-ref"));
      //3.配置cache
      cacheElement(context.evalNode("cache"));
      //4.配置parameterMap(已经废弃,老式风格的参数映射)
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //5.配置resultMap(高级功能)
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //6.配置sql(定义可重用的 SQL 代码段)
      sqlElement(context.evalNodes("/mapper/sql"));
      //7.配置select|insert|update|delete 这里是重点!!!!
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

  //构建语句
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      //构建所有语句,一个mapper下可以有很多select
      //语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder,前面三个Builder对象剩下一个未介绍的,这里来了。
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
          //核心 XMLStatementBuilder.parseStatementNode 
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
          //如果出现SQL语句不完整,把它记下来,塞到configuration去
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

下面是具体到如何将一个xml中的 sql封装为一个MappedStatement 对象

  //解析语句(select|insert|update|delete)
//<select
//  id="selectPerson"
//  parameterType="int"
//  parameterMap="deprecated"
//  resultType="hashmap"
//  resultMap="personResultMap"
//  flushCache="false"
//  useCache="true"
//  timeout="10000"
//  fetchSize="256"
//  statementType="PREPARED"
//  resultSetType="FORWARD_ONLY">
//  SELECT * FROM PERSON WHERE ID = #{id}
//</select>
  public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    //如果databaseId不匹配,退出
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    //暗示驱动程序每次批量返回的结果行数
    Integer fetchSize = context.getIntAttribute("fetchSize");
    //超时时间
    Integer timeout = context.getIntAttribute("timeout");
    //引用外部 parameterMap,已废弃
    String parameterMap = context.getStringAttribute("parameterMap");
    //参数类型
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    //引用外部的 resultMap(高级功能)
    String resultMap = context.getStringAttribute("resultMap");
    //结果类型
    String resultType = context.getStringAttribute("resultType");
    //脚本语言,mybatis3.2的新功能
    String lang = context.getStringAttribute("lang");
    //得到语言驱动
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    //结果集类型,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种
    String resultSetType = context.getStringAttribute("resultSetType");
    //语句类型, STATEMENT|PREPARED|CALLABLE 的一种
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    //获取命令类型(select|insert|update|delete)
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    //是否要缓存select结果
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    //仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。
    //这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    //解析之前先解析<include>SQL片段
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    //解析之前先解析<selectKey>
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    //解析成SqlSource,一般是DynamicSqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    //(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
    String keyProperty = context.getStringAttribute("keyProperty");
    //(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

	//又去调助手类
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

最后将mapperstatement对象存到 configuration.addMappedStatement(statement); 统一管理。
我们来debug看下最终生成的mapperstatement都有些什么参数。

在这里插入图片描述
下面再看看sqlSource对象。
在这里插入图片描述
最终所有的sql都会解析到Configuration对象里面
在这里插入图片描述

扫描二维码关注公众号,回复: 11567969 查看本文章

猜你喜欢

转载自blog.csdn.net/ygy982883422/article/details/106456542