重新學習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()是如何執行的呢?

先來溫習一下代理模式

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