Mybatis 深入浅出 -- 初始化篇

Mybatis

本文从mybatis 初始化流程开始,根据源码逐步剖析

首先先思考一下这几个问题

  1. Mybatis解决了什么问题? 无非是简化数据库操作、实现封装、让程序员更关注SQL本身、维护便利
  2. 它是如何解决这些问题的?
  3. Mybatis是运行在什么样的环境下的?
  4. 它如何读取解析用户定义的配置信息?即如何初始化的
  5. 它的环境结构是什么样的?
  6. 在这一环境下如何 实现做增删查改
  7. 如何执行SQL
  8. 如何执行动态SQL
  9. 如何拼接查询参数等等

我知道了这些原理后,我能做些什么?

例如用 Mybatis来扩展一个分页插件
Mybatis存在哪些我觉得可以改进的地方


正经的分割线


带着这些问题,我们从一个TestCase开始

传统jdbc流程

  1. 注册驱动
  2. 获取连接
  3. 准备SQL执行statement
  4. 设置SQL参数
  5. 执行SQL,获取到结果集
  6. 解析结果集
  7. 依次关闭结果集,statement,数据库连接
    Class.forName(Driver.class.getName());
    Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?characterEncoding=utf-8", "root", "root");
    PreparedStatement preparedStatement = connection.prepareStatement("select * from test where id=?");
    preparedStatement.setString(1,"1");
    ResultSet resultSet = preparedStatement.executeQuery();
    while (resultSet.next()) {
      String columnName1 = resultSet.getMetaData().getColumnName(1);
      String columnName2 = resultSet.getMetaData().getColumnName(2);
      System.out.println(columnName1+":"+resultSet.getString(1));
      System.out.println(columnName2+":"+resultSet.getString(2));
    }
    resultSet.close();
    preparedStatement.close();
    connection.close();

Mybatis初始化,主体流程

  1. 解析config.xml,读取主配置文件,解析<configuration>及下所有内容
  2. 获取 设置的 别名对象工厂对象反射信息
  3. 解析 各个mappers 信息, 包括:xml注解形式, 封装成 MapperedStatement 放入Map中待用, 解析信息包括:
  4. 自定义的缓存标签设置解析
  5. 自定义的参数映射解析
  6. resultMap标签映射配置
  7. sql标签解析
  8. 解析各个CRUD 标签

先从一段测试代码开始,以xml配置为例(注解方式流程解析思想都相同)

注意 阅读 序号 (1)、(2)、(3)、(4)、(5)、(6)
Test.java

    (1)通过SqlSessionFactoryBuilder构建mybatis初始化组件,见下一代码栏 解释
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(“xxx/mybatis.xml”);
    // 使用一个SqlSession作为此次连接, 主要讲这儿,初始化流程
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 动态代理 执行SQL
    CustomMapper mapper = sqlSession.getMapper(DemoMapper.class);
    Map<String,Object> map = new HashMap<>();
    map.put("id","1");
    // 自定义了一个select方法,xml的形式
    System.out.println(mapper.select(map));
    sqlSession.close();

SqlSessionFactoryBuilder

    
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //解析config.xml(这里解析使用的是  java dom)
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //parse(): 解析config.xml里面的节点
      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


  (2)如果解析过就不能再次解析mybatis主文件,将mybatis主文件中,configuration标签及其下的所有内容解析出来,见(3)
  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  (3)
  private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 将xml中定义的别名,存入当前builder的typeAliasRegistry -- Map中
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      // 设置对象工厂,自定义返回对象的哪种实例
      objectFactoryElement(root.evalNode("objectFactory"));
      //MateObject 方便反射操作实体类的对象
      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"));
      // 解析mapper到map存储, 重点解析这儿,见(4)
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

XMLMapperBuilder

  (4)
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 解析mapper.xml 见(5)
      configurationElement(parser.evalNode("/mapper"));
      // 记录加载过的resource
      configuration.addLoadedResource(resource);
      //绑定Namespace里面的Class对象
      bindMapperForNamespace();
    }

    //重新解析之前解析不了的节点
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
  
  (5)
  // 解析mapper文件里面的节点,封装成MapperedStatement
  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");
      }
      builderAssistant.setCurrentNamespace(namespace);
      // 缓存设置解析
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      // 参数映射解析
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // resultMap映射解析
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // sql语句解析
      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);
    }
  }

  // 接上面
  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    // 遍历所有CRUD节点
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 解析xml中的节点, 见下面(6)
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        // xml语句有问题时 存储到集合中 等解析完能解析的再重新解析
        // 比如insert中依赖了 某个 select标签的,但是insert是按照先后顺序解析的,就会造成insert解析时,缺少select的MapperedStatement,从而抛出异常
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

XMLStatementBuilder

  (6)
  public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // 是否是select命令
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 如果是查询则不刷新,不是则刷
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // 如果是查询则使用二级缓存,是则不使用,即同一个namespace都共享缓存
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    // 是否需要处理嵌套查询结果 将sql语句处理成嵌套形式的
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 替换Includes标签为对应的sql标签里面的值
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    // 解析自定义的多语言配置
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    // 解析selectKey标签
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    //设置主键自增规则
    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))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
    //解析Sql  根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    // 解析单个mapper.xml 结束 
    // 添加到 Configuration中的 mappedStatements 集合中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

Mybatis初始化,主体流程

结合上面文字描述的流程,这里给出一张简图,能快速总结:


Mybatis初始化流程.png

至此 Mybatis 的初始化解析结束

下篇文章将逐步剖析 Mybtis的执行原理

下篇文章传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。