重新学习Mybatis(三)

第二篇分析了Mybatis是如何把SQL和参数根据TyperHandler一一设值,并且如何实例化PrepareStatement对象的,和获得数据库连接的。

一开始我看源码的时候我在思考一个问题,Mybatis是在什么时候?如何拿到这些关键的信息的?主要就是第一篇中的至关重要的MapperStatement对象的

在前两篇的文章中,是直接在SqlSession.insert()的时候直接debug进去的,那个时候就有了MapperStatement对象了。那么MapperStatement肯定在这段代码执行之前。那么就是这几行代码中。

 String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
 SqlSessionFactory factory = builder.build(inputStream);
        SqlSession sqlSession = factory.openSession();

debug进入builder.build,你要是问我怎么就知道进这里debug,那只有是看方法名猜的,就是这样。

XMLConfigBuilder一看这个名字就猜得到是解析Mybatis的配置文件的,接着往下走。进入paresr.pares()方法

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("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);
    }
  }

这些名字是不是特别熟悉,就是第一篇里的mybatis-config.xml里的标签,可以知道,Mybatis这里解析各个标签。这里重点关注mapperElement这个方法这里就是解析我们配置的mapper的方法。进入

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 {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            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.");
          }
        }
      }
    }
  }

可以看到,这里Mybatis循环解析<Mappers>标签下的<Mapper>标签里的xml文件,如果你是批量配置,则直接配置package,这里先不说这种方式。这里第一篇里的mybatis-config.xml文件中<mapper>标签配置的是resource元素,所以这里会判断进入对应的resource解析块中

接下来重点关注mapperParser.parse()方法。里面这三行代码是关键的,一个一个来看。

第一、configrationElement()这个方法。

拿到当前mapper.xml(UserMapper.xml)的namespace。接下来会执行buildStatementFromContext(context.evalNodes)这个方法

意思是构建Statement从当前的这个XNode中,感觉这就是本篇的重点,终于找到了,MapperStatement对象应该就是这里实例化的。里面的context.evalNodes是解析这个当前的UserMapper.xml这个文件中有几个SQL标签。

源码如下:

  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) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

这里Mybatis拿到了当前UserMapper.xml文件里所有的、最原始的我们写的SQL语句,通过for(XNode: context:list)循环并一个个解析每个sql标签。这里拿第一个insert语句进行举例。进入statementParser.parseStatementNode()。进入方法之前,可以根据方方法名字可知,这里每一个SQL标签节点对应一个StatementNode,在最原始里的传统的JDBC中就是一个SQL,一个Statement,所以很好理解。debug进入。

这里清楚的看到,Mybatis解析从当前SQL标签中解析出insert,并将sqlCommandType设置为Insert,这里的id就是UserMapper.xml中的<insert>标签的ID,接下来

可以看到,这里将原始的SQL语句转换成values是问号(?)的形式,相当于把insert into user(id,name,age) values(#{id},#{name},#{age})转换为insert into user(id,name,age) values(?,?,?);这里是否想到传统的JDBC的我们书写的时候样子?

然后实例化为sqlSource对象第二篇曾经留下疑问,这个SQLSource就是在这里实例化的

并且这里Mybatis默认设置的statementTyper就是prepared类型的。接下来

在这个方法里进行MapperStatement的初始化。终于找到它了。源码里可以看到,实例化MapperStatement通过statementBuilder.build()方法。这样一个MapperStatement就实例化好了,接下里就是把这个对象放到configration中,这样我们在第一篇里说的获取至关重要的mapperStaement对象就可以拿到了。

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

    MappedStatement statement = statementBuilder.build();
    //在这里将实例化好的statement对象放到configuration中,便于我们第二种方式时候使用
    configuration.addMappedStatement(statement);
    return statement;
  }

第二、bindMapperForNamespace,这个方法比较简单,就是把当前maper的完整路径,放到configuration中,便于我们第一篇里说的Mybatis的第一种方式执行时候调用。

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

这样最后,build(paresr.parse())里面的方法执行完毕之后,实例化SqlSessionfactoty对象就可以在下一步调用openSession()获得sqlSession对象进行前两篇中的操作了。

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

到此为止,基本上Mybatsi上基本流程,从头到尾,都差不多用肉眼看到了。那么Mybatis另外方式SqlSession.getMapper()是如何执行的呢?

先来温习一下代理模式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章