mybatis(一):加載和解析配置文件

在這里記錄一下自己學(xué)習(xí)mybatis源碼過程中的一些學(xué)習(xí)體會,文章內(nèi)容基于mybatis3.5.3-SNAPSHOT:

下面是mybatis一個測試用例中配置文件的截圖,配置文件詳情參考mybatis中文官網(wǎng)

image

1.事例

下面是mybatis測試用例中加載配置文件,并且運(yùn)行的過程,這篇文章主要記錄一下mybatis加載配置文件的過程

@BeforeAll
static void setUp() throws Exception {
// create a SqlSessionFactory
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/permissions/mybatis-config.xml")) {
  sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}

// populate in-memory database
BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
        "org/apache/ibatis/submitted/permissions/CreateDB.sql");
}

從以上的實(shí)例代碼可以看到關(guān)于mybatis讀取默認(rèn)配置文件的過程,接下來就是詳細(xì)的看看整體的過程。

2.源碼分析

2.1創(chuàng)建SqlSessionFactory

SqlSession是mybatis的關(guān)鍵,這個接口包含了sql執(zhí)行,事務(wù),緩存等許多的方法。要獲取SqlSession就要先得到SqlSessionFactory。為了得到SqlSessionFactory就需要使用SqlSessionFactoryBuilder來解析配置文件,SqlSessionFactoryBuilder有多個build方法,基本一致,挑一個來看看。

public SqlSessionFactorybuild(Reader reader, String environment, Properties properties) {

try {
    // 創(chuàng)建 XMLConfigBuilder 對象,底層使用的是jdk的XPath解析xml文件
    XMLConfigBuilder parser =new XMLConfigBuilder(reader, environment, properties);
    // 執(zhí)行 XML 解析
    // 創(chuàng)建 DefaultSqlSessionFactory 對象
    return build(parser.parse());
  }catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  }finally {
ErrorContext.instance().reset();
    try {
reader.close();
    }catch (IOException e) {
    // Intentionally ignore. Prefer previous error.
    }
}

}

2.2 解析配置文件

下面我們來看看parser.parse():

public Configurationparse() {
  // 判斷是否已經(jīng)加載過
  if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed =true;
  // 解析configuration節(jié)點(diǎn)
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

最重要的是parseConfiguration方法:

private void parseConfiguration(XNode root) {
try {
    //issue #117 read properties first
    // 屬性
    propertiesElement(root.evalNode("properties"));
    // 設(shè)置,這是 MyBatis 中極為重要的調(diào)整設(shè)置,它們會改變 MyBatis 的運(yùn)行時行為
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    // 加載自定義 VFS 實(shí)現(xiàn)類
    loadCustomVfs(settings);
    // 指定 MyBatis 所用日志的具體實(shí)現(xiàn),未指定時將自動查找
    loadCustomLogImpl(settings);
    // 類型別名,為 Java 類型設(shè)置一個短的名字
    typeAliasesElement(root.evalNode("typeAliases"));
    // 插件,在已映射語句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用
    pluginElement(root.evalNode("plugins"));
    // 對象工廠,MyBatis 每次創(chuàng)建結(jié)果對象的新實(shí)例時,它都會使用一個對象工廠實(shí)例來完成
    objectFactoryElement(root.evalNode("objectFactory"));
    // 對象包裝工廠
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    // 反射工廠
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    // 設(shè)置settings屬性到configuration中,沒有時設(shè)置默認(rèn)配置
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    // 環(huán)境配置
    environmentsElement(root.evalNode("environments"));
    // 數(shù)據(jù)庫廠商標(biāo)識
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    // 類型處理器,MyBatis 在預(yù)處理語句(PreparedStatement)中設(shè)置一個參數(shù)時,
    // 還是從結(jié)果集中取出一個值時, 都會用類型處理器將獲取的值以合適的方式轉(zhuǎn)換成 Java 類型
    typeHandlerElement(root.evalNode("typeHandlers"));
    // SQL 映射語句
    mapperElement(root.evalNode("mappers"));
  }catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

大多數(shù)都是屬性的設(shè)置,最終所有的設(shè)置都會配置到XMLConfigBuilder以及父類BaseBuilder的屬性對象中,其中mapperElement方法是解析mapper.xml,即我們的mapper.xml文件或者*mapper.java接口(針對在java文件中通過注解創(chuàng)建sql和加上一些配置等)。

2.3 xml文件以及接口解析

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 如果是配置的package那就掃描包,針對已經(jīng)在方法上使用注解實(shí)現(xiàn)功能
        <1>
        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");
          // 解析本地的xml文件
          <2>
          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();
          }
          // 解析遠(yuǎn)程地址上的xml文件
          <3>
          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();
          }
          // 單個文件解析,也是針對已經(jīng)在方法上使用注解實(shí)現(xiàn)功能
          <4>
          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.");
          }
        }
      }
    }
  }

<1>,<2>,<3>,<4>處的代碼,最終的解析方式都是解析解析xml的同時解析對應(yīng)的接口內(nèi)的方法,或者是先解析接口內(nèi)的方法再解析接口對應(yīng)的xml文件
configuration.addMappers,先來看下MapperRegistry.addMapper方法:

public <T> void addMapper(Class<T> type) {
    // 判斷必須是接口
    if (type.isInterface()) {
      // 判斷是否解析過
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // 用于判斷是否解析過
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        <1>
        parser.parse();
        loadCompleted = true;
      } finally {
        // 解析錯誤,留到后面解析
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

<1>處代碼最關(guān)鍵

public void parse() {
    String resource = type.toString();
    // 判斷是否加載過
    if (!configuration.isResourceLoaded(resource)) {
      // 加載對應(yīng)的*mapper.xml文件
      <1>
      loadXmlResource();
      // 用于判斷是否加載
      configuration.addLoadedResource(resource);
      // 設(shè)置當(dāng)前命名空間,如果與當(dāng)前命名空間不一致,拋出錯誤
      // 我理解可能是防止多線程下同時解析不同文件
      assistant.setCurrentNamespace(type.getName());
      // 解析@CacheNamespace,二級緩存相關(guān)
      parseCache();
      // 解析@CacheNamespaceRef,二級緩存相關(guān)
      parseCacheRef();
      Method[] methods = type.getMethods();
      // 遍歷每個方法,解析其上的注解
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            <2>
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          // 解析失敗,添加到 configuration 中
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    // 解析上面for循環(huán)解析失敗的方法
    parsePendingMethods();
  }

其中<1>處代碼是解析xml文件的,<2>處代碼是解析對應(yīng)的java接口
先來看看<1>處代碼是怎么找到并且解析xml文件的

private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    // 判斷是否加載過
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      // 獲取當(dāng)前對應(yīng)的xml的路徑
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      // #1347
      // 獲取當(dāng)前模塊中的xml文件流對象
      InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
      if (inputStream == null) {
        // Search XML mapper that is not in the module but in the classpath.
        try {
          // 獲取不在當(dāng)前模塊,但是在對應(yīng)路徑下的xml文件
          inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
        } catch (IOException e2) {
          // ignore, resource is not required
        }
      }
      if (inputStream != null) {
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

繼續(xù)來看看XMLMapperBuilder.parse()是如何解析xml文件的。

2.3.1解析xml

public void parse() {
    // 如果沒有加載過
    if (!configuration.isResourceLoaded(resource)) {
      // 解析xml文件中的所有標(biāo)簽
      <1>
      configurationElement(parser.evalNode("/mapper"));
      // 標(biāo)記該 Mapper 已經(jīng)加載過
      configuration.addLoadedResource(resource);
      // 解析對應(yīng)的*mapper.java文件,
      // 解析xml或者java文件的時候都會去解析對應(yīng)的另外一個文件
      // 在解析對應(yīng)的文件時都要判斷是否已經(jīng)解析過
      bindMapperForNamespace();
    }

    // 解析待定的 <resultMap /> 節(jié)點(diǎn)
    parsePendingResultMaps();
    // 解析待定的 <cache-ref /> 節(jié)點(diǎn)
    parsePendingCacheRefs();
    // 解析待定的 SQL 語句的節(jié)點(diǎn)
    parsePendingStatements();
  }

重點(diǎn)看看<1>處的代碼,是如何解析整個xml文件中的所有節(jié)點(diǎn)的,最后面的3個方法是繼續(xù)嘗試解析前面解析xml文件時沒有解析成功的節(jié)點(diǎn)。

private void configurationElement(XNode context) {
    try {
      // 獲得 namespace 屬性
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      // 設(shè)置 namespace 屬性
      builderAssistant.setCurrentNamespace(namespace);
      // 解析 <cache-ref> 節(jié)點(diǎn)
      cacheRefElement(context.evalNode("cache-ref"));
      // 解析 <cache> 節(jié)點(diǎn)
      cacheElement(context.evalNode("cache"));
      // 已廢棄!老式風(fēng)格的參數(shù)映射。內(nèi)聯(lián)參數(shù)是首選,這個元素可能在將來被移除,這里不會記錄。
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // <1> 解析 <resultMap> 節(jié)點(diǎn)們,解析成resultMap對象保存在 configuration 中
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析 <sql> 節(jié)點(diǎn)們,保存id和node對應(yīng)關(guān)系到 sqlFragments 中
      sqlElement(context.evalNodes("/mapper/sql"));
      // <2> 解析 <select> <insert> <update> <delete> 節(jié)點(diǎn)們
      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);
    }
  }

這里比較復(fù)雜的是<1>和<2>處的解析,別的比較簡單,可自行看一下。

2.3.1.1resultMapElements(context.evalNodes("/mapper/resultMap"))

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 獲取 type
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    // 替換別名,獲取 class 對象
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>();
    resultMappings.addAll(additionalResultMappings);
    // 解析子節(jié)點(diǎn)
    List<XNode> resultChildren = resultMapNode.getChildren();
    // 解析所有的節(jié)點(diǎn)到 resultMappings 中
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      // 保存 resultMap 到 configuration 中
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

此處主要是解析<resultMap>節(jié)點(diǎn),構(gòu)造resultMap對象,并且保存到 configuration中

2.3.1.2buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

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

public void parseStatementNode() {
    // 例如<select id="">
    String id = context.getStringAttribute("id");
    // 例如<select databaseId="">
    String databaseId = context.getStringAttribute("databaseId");

    // 判斷當(dāng)前節(jié)點(diǎn)是否已經(jīng)解析過 以及 判斷databaseId是否相等
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    // 獲取是什么類型的節(jié)點(diǎn) 如 :select
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    // 解析sql的 include 片段
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 獲取 parameterType 以及 別名轉(zhuǎn)換
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    // 國際化相關(guān)
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    // 在 configuration 中保存 KeyGenerator,用于自動生成主鍵
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    // 返回 currentNamespace + "." + id + "!selectKey"
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      // 如果沒有 <selectKey> 節(jié)點(diǎn),就查看 該節(jié)點(diǎn)是否配置了 useGeneratedKeys,
      // 或者配置文件配置了 useGeneratedKeys 并且 該語句為 insert
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 如果是沒有動態(tài)標(biāo)簽的sql,#{}會解析為?,并且保存對應(yīng)的屬性
    // 如果是動態(tài)標(biāo)簽的sql,按sql的順序拆分sql為 單個 sqlNode(ifSqlNode,forEachSqlNode等)或者是MixedSqlNode包含多個SqlNode
    // 以及sql對應(yīng)的 prefix,subffix,prefixesToOverride等屬性
    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");

    // configuration 中保存 MappedStatement
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

最終新增的MappedStatement對象代表的是xml一個sql標(biāo)簽,包含這個sql的所有配置以及sql語句等屬性。

2.3.2 解析java接口

代碼入口在MapperAnnotationBuilder.parse()中的parseStatement(method)方法,這就是解析接口中的每個方法以及方法上的注解的。

  void parseStatement(Method method) {
    // 獲取非分頁的形參類型,多個參數(shù)用 ParamMap 表示
    Class<?> parameterTypeClass = getParameterType(method);
    // 注解式的動態(tài)sql
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 獲取 sqlsourse 對象
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      Options options = method.getAnnotation(Options.class);
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = configuration.getDefaultResultSetType();
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      // 是否是 select sql 語句
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      // 如果不是 select 默認(rèn)開始二級緩存
      boolean flushCache = !isSelect;
      // select 默認(rèn)開啟一級緩存
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = null;
      String keyColumn = null;
      // 獲取生成自動主鍵的 keyGenerator
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else if (options == null) {
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        } else {
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      } else {
        keyGenerator = NoKeyGenerator.INSTANCE;
      }

      if (options != null) {
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = true;
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = false;
        }
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        if (options.resultSetType() != ResultSetType.DEFAULT) {
          resultSetType = options.resultSetType();
        }
      }

      String resultMapId = null;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {
        resultMapId = String.join(",", resultMapAnnotation.value());
      } else if (isSelect) {
        resultMapId = parseResultMap(method);
      }

      // 構(gòu)造 mappedstatement configuration 中
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }

大致的解析解析過程就是這樣的,總的來說就是解析所有的配置文件組成一個configuration對象,然后會調(diào)用SqlSessionFactory的build方法new 一個DefaultSqlSessionFactory對象,并且設(shè)置configuration,configuration幾乎包含了mybatis所有的屬性,貫穿幾乎所有的mybatis流程。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容