mybatis源码学习(三):源码解读之加载解析mapper.xml配置文件

源码解读:解析mapper.xml配置

  • mapper.xml配置文件结构说明
  • 源码解读
    • 加载mapper.xml的方式
    • 解析mapper.xml
    • 解析resultMap标签和sql(select|insert...)标签
      • resultMap标签
      • sql标签(select|insert|update|delete)
    • 核心类说明
      • XMLMapperBuilder
      • ResultMapResolver
      • ResultMap
      • ResultMapping
      • SqlCommandType
      • MappedStatement

mapper.xml配置文件结构说明

  • mapper(映射配置)
    • cache – 对给定命名空间的缓存配置。

    • cache-ref – 对其他命名空间缓存配置的引用。

    • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。

    • parameterMap – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。

    • sql – 可被其他语句引用的可重用语句块。

    • insert – 映射插入语句

    • update – 映射更新语句

    • delete – 映射删除语句

    • select – 映射查询语句

源码解读

接着上一节往下走:配置mapper节点的方式有四种:package,resource,url,class 由于项目中常用的是xml,这里详细解读resource方式加载mapper

加载mapper.xml的方式

/**
 * 解析mappers节点,加载mapper配置
 */
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    //变量所有的mapper.xml配置有多个
    for (XNode child : parent.getChildren()) {
     //如果mapper是,配置mapper的四种方式:package,resource,url,class
      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方式配置mapper
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          //读取配置:mapper.xml
          InputStream inputStream = Resources.getResourceAsStream(resource);
          //通过mapper.xml构建xml解析对象
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          //解析mapper.xml
          mapperParser.parse();
        //通过url的方式配置mapper
        } 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();
        //通过class的方式配置mapper
        } 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.");
        }
      }
    }
  }
}

解析mapper.xml

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");
    }
    //保持并校验namespace
    builderAssistant.setCurrentNamespace(namespace);
    //解析cache-ref标签 属性namespace相同的缓存将共享
    cacheRefElement(context.evalNode("cache-ref"));
    //解析cache标签
    cacheElement(context.evalNode("cache"));
    //解析paramteterMap标签 (由于该标签已被启用,就不解读了)
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    //解析resultMap标签
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    //解析sql标签
    sqlElement(context.evalNodes("/mapper/sql"));
    //解析select|insert|update|delete标签
    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);
  }
}

解析resultMap标签和sql(select|insert…)标签

resultMap标签

由于解析标签的方法类似,这里取最常用的resultMap和sql标签解读
先看看官方属性说明:
mybatis源码学习(三):源码解读之加载解析mapper.xml配置文件_第1张图片
源码:

/**
 * 解析resultMap标签及其子节点
 */
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
  ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
  //获取type属性 如果没有则获取 ofType > ofType > resultType > javaType
  String type = resultMapNode.getStringAttribute("type",
      resultMapNode.getStringAttribute("ofType",
          resultMapNode.getStringAttribute("resultType",
              resultMapNode.getStringAttribute("javaType"))));
  //获取type类型的class对象
  Class<?> typeClass = resolveClass(type);
  // association和case可能会进入
  if (typeClass == null) {
    typeClass = inheritEnclosingType(resultMapNode, enclosingType);
  }
  Discriminator discriminator = null;
  List<ResultMapping> resultMappings = new ArrayList<>();
  resultMappings.addAll(additionalResultMappings);
  //获取所有子节点 id,result,constructor...
  List<XNode> resultChildren = resultMapNode.getChildren();
  for (XNode resultChild : resultChildren) {
    //构造器
    if ("constructor".equals(resultChild.getName())) {
      processConstructorElement(resultChild, typeClass, resultMappings);
    } else if ("discriminator".equals(resultChild.getName())) { //使用结果值来决定使用哪个 resultMap
      discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
    } else {
      //着重解读 id和result标签统一处理,id会额外添加一个枚举标记
      List<ResultFlag> flags = new ArrayList<>();
      if ("id".equals(resultChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      //ResultMapping对象对应mapper.xml中的ResultMap的子节点
      resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
    }
  }
  //获取id,extends,autoMapping属性
  String id = resultMapNode.getStringAttribute("id",
          resultMapNode.getValueBasedIdentifier());
  String extend = resultMapNode.getStringAttribute("extends");
  Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
  //ResultMapResolver对象用于构建 ResultMap对应mapper.xml中的ResultMap节点
  ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
  try {
    //返回构建ResultMap对象
    return resultMapResolver.resolve();
  } catch (IncompleteElementException  e) {
    configuration.addIncompleteResultMap(resultMapResolver);
    throw e;
  }
}

/**
 * 构建ResultMapping
 */
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
  String property;
  //获取属性,构建ResultMapping对象
  if (flags.contains(ResultFlag.CONSTRUCTOR)) {
    property = context.getStringAttribute("name");
  } else {
    property = context.getStringAttribute("property");
  }
  String column = context.getStringAttribute("column");
  String javaType = context.getStringAttribute("javaType");
  String jdbcType = context.getStringAttribute("jdbcType");
  String nestedSelect = context.getStringAttribute("select");
  String nestedResultMap = context.getStringAttribute("resultMap",
      processNestedResultMappings(context, Collections.emptyList(), resultType));
  String notNullColumn = context.getStringAttribute("notNullColumn");
  String columnPrefix = context.getStringAttribute("columnPrefix");
  String typeHandler = context.getStringAttribute("typeHandler");
  String resultSet = context.getStringAttribute("resultSet");
  String foreignColumn = context.getStringAttribute("foreignColumn");
  boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
  //获取java的数据类型
  Class<?> javaTypeClass = resolveClass(javaType);
  Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
  //获取jdbc的数据类型
  JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
  return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}

sql标签(select|insert|update|delete)

/**
 * 解析sql节点并保存到全局配置(configuration)中
 */
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();
  //获取sql的类型 UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
  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之前确认是否包含标签
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());
  //获取参数类型
  String parameterType = context.getStringAttribute("parameterType");
  //获取参数class
  Class<?> parameterTypeClass = resolveClass(parameterType);
  //获取语言格式驱动 默认使用Xml XMLLanguageDriver
  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  // Parse selectKey after includes and remove them.
  //在include之后解析selectKey并删除它们。
  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre:  and  were parsed and removed)
  //解析SQL (pre: 被解析和删除)
  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 XMLLanguageDriver
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  //将会传入这条语句的参数类的完全限定名或别名
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  //这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)。
  Integer fetchSize = context.getIntAttribute("fetchSize");
  //这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
  Integer timeout = context.getIntAttribute("timeout");
  //这是引用外部 parameterMap
  String parameterMap = context.getStringAttribute("parameterMap");
  //从这条语句中返回的期望类型的类的完全限定名或别名。
  String resultType = context.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  //外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。
  //可以使用 resultMap 或 resultType,但不能同时使用。
  String resultMap = context.getStringAttribute("resultMap");
  //FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖驱动)。
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  //(仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值
  // 或者通过 insert 语句的 selectKey 子元素设置它的键值
  String keyProperty = context.getStringAttribute("keyProperty");
  //(仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,
  // 当主键列不是表中的第一列的时候需要设置。
  String keyColumn = context.getStringAttribute("keyColumn");
  //这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。
  String resultSets = context.getStringAttribute("resultSets");
  //解析sql标签信息并保存到全局配置中去 MappedStatement
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

sql语句中等标签在调用sql语句执行的时候才能解析取值

代码到这里,mybatis初始化配置文件完成,后续就是通过SqlSession操作我们初始化的sql了

核心类说明

XMLMapperBuilder

XML配置解析器(用于解析:mapper.xml)

ResultMapResolver

ResultMapResolver对象用于构建ResultMap对象

ResultMap

对应mapper.xml中的ResultMap节点

ResultMapping

ResultMapping对象对应着mapper.xml中的ResultMap>Result,Id,constructor…

SqlCommandType

sql命令的类型

MappedStatement

保存解析后的sql(select|insert|update|delete)语句信息

下面类对应mapper.xml中的各类标签
mybatis源码学习(三):源码解读之加载解析mapper.xml配置文件_第2张图片

你可能感兴趣的:(Mybatis,JAVA)