欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

mybatis源码学习------resultMap和sql片段的解析

程序员文章站 2022-07-12 22:49:25
...

resultMap的解析

书接上回,mybatis对于<resultMap></resultMap>标签解析的方法入口为:

mybatis源码学习------resultMap和sql片段的解析

resultMapElements(List list)

根据mybatis-3-mapper.dtd文件中对于resultMap的定义可知,一个mapper节点内可以定义任意多个resultMap节点,所以resultMapElements方法会遍历所有的resultMap进行解析,代码如下:

private void resultMapElements(List<XNode> list) {
  for (XNode resultMapNode : list) {
    try {
      resultMapElement(resultMapNode);
    } catch (IncompleteElementException e) {
      // ignore, it will be retried
    }
  }
}

resultMapElement(XNode)

调用重载方法,三个参数的resultMapElement方法主要用于后续递归调用,所以这里单参数的resultMapElement方法更像是一个“桥接”方法。

private ResultMap resultMapElement(XNode resultMapNode) {
  return resultMapElement(resultMapNode, Collections.emptyList(), null);
}

resultMapElement(XNod, List, Class)方法

resultMapElement方法会为<resultMap></resultMap>中配置的每一个或标签都创建一个ResultMapping对象,并将其保存在一个集合中,最后传递给ResultMapResolver对象进行解析。

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
  ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
  //因为<resultMap/>标签的配置存在嵌套的情况,所以在代码处理嵌套情况的时候一定会使用递归,
  //这里获取默认值的逻辑主要是为了处理<resultMap/>嵌套<resultMap/>的情况
  String type = resultMapNode.getStringAttribute("type",
    resultMapNode.getStringAttribute("ofType",
      resultMapNode.getStringAttribute("resultType",
        resultMapNode.getStringAttribute("javaType"))));
  //解析type属性所配置的类
  Class<?> typeClass = resolveClass(type);
  if (typeClass == null) {
    typeClass = inheritEnclosingType(resultMapNode, enclosingType);
  }
  Discriminator discriminator = null;  //resultMappings集合中维护了当前<resultMap/>中所有的映射关系
  List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
  List<XNode> resultChildren = resultMapNode.getChildren();
  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));
    }
  }
  //获取resultMap节点配置的ID
  //getValueBasedIdentifier()方法返回的格式为    类型1[ID1]_类型2[ID2]......
  String id = resultMapNode.getStringAttribute("id",
    resultMapNode.getValueBasedIdentifier());
  //获取resultMap节点配置的继承关系
  String extend = resultMapNode.getStringAttribute("extends");
  //获取是否开启自动映射配置
  Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
  //创建ResultMap解析器实例
  ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
  try {
    //将具体的解析任务委派给MapperBuilderAssistant的实例
    return resultMapResolver.resolve();
  } catch (IncompleteElementException e) {
    configuration.addIncompleteResultMap(resultMapResolver);
    throw e;
  }
}

解析<constructor/>

processConstructorElement方法对构造函数节点中的ID进行了特殊的标识,便于在后续的解析中区分对id和idArg进行区分,如下图所示,:

mybatis源码学习------resultMap和sql片段的解析

private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
  List<XNode> argChildren = resultChild.getChildren();
  for (XNode argChild : argChildren) {
    List<ResultFlag> flags = new ArrayList<>();
    //进行标记
    flags.add(ResultFlag.CONSTRUCTOR);
    if ("idArg".equals(argChild.getName())) {
      flags.add(ResultFlag.ID);
    }
    resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
  }
}

调用buildResultMappingFromContext方法进行解析

buildResultMappingFromContext方法

从功能上说,本方法只负责将用户在xml中配置的信息读取出来,解析的部分则交给MapperBuilderAssistant#buildResultMapping去完成

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
  String property;
  if (flags.contains(ResultFlag.CONSTRUCTOR)) {
    //获取构造方法形参的名字的配置。从 3.4.3 版本开始,通过指定具体的参数名,你可以以任意顺序写入 arg 元素
    property = context.getStringAttribute("name");
  } else {
    //获取映射到列结果的字段或属性的配置
    property = context.getStringAttribute("property");
  }
  //获取数据库中的列名,或者是列的别名的配置
  String column = context.getStringAttribute("column");
  //获取Java类的全限定名,或一个类型别名的配置
  String javaType = context.getStringAttribute("javaType");
  //获取对应的JDBC类型的配置
  String jdbcType = context.getStringAttribute("jdbcType");
  //获取嵌套查询映射语句的ID
  String nestedSelect = context.getStringAttribute("select");
  //获取嵌套结果集映射的ID,
  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"));
  //根据用户配置解析javaType对应的类型
  Class<?> javaTypeClass = resolveClass(javaType);
  //根据用户配置解析typeHandler对应的类型
  Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
  //根据用户配置解析jdbcType对应的类型
  JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
  //委派给BuilderAssistant实例对其进行进一步的解析
  return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}

MapperBuilderAssistant#buildResultMapping

该方法主要对上一步解析出来的配置进行实例化,之后再调用ResultMapping.Builder#build()方法对用户的配置做进一步的解析并创建对应的ResultMapping对象。

public ResultMapping buildResultMapping(
    Class<?> resultType,
    String property,
    String column,
    Class<?> javaType,
    JdbcType jdbcType,
    String nestedSelect,
    String nestedResultMap,
    String notNullColumn,
    String columnPrefix,
    Class<? extends TypeHandler<?>> typeHandler,
    List<ResultFlag> flags,
    String resultSet,
    String foreignColumn,
    boolean lazy) {
  //对结果类型进行类型推断
  Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
  //对用户配置的类型拦截器进行实例化
  TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
  //composites集合主要保存column属性拆分后生成的结果,在嵌套查询的模式下,column的配置会作为参数传递给目标select语句
  List<ResultMapping> composites;
  if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
    composites = Collections.emptyList();
  } else {
    composites = parseCompositeColumnName(column);
  }
  //使用建造者设计模式创建ResultMapping实例
  return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
      .jdbcType(jdbcType)
      .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
      .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
      .resultSet(resultSet)
      .typeHandler(typeHandlerInstance)
      .flags(flags == null ? new ArrayList<>() : flags)
      .composites(composites)
      .notNullColumns(parseMultipleColumnNames(notNullColumn))
      .columnPrefix(columnPrefix)
      .foreignColumn(foreignColumn)
      .lazy(lazy)
      .build();
}

ResultMapping.Builder#build()方法

该方法的主要逻辑是对用户的配置进行各种校验,因为无论当前解析的是

public ResultMapping build() {
  // lock down collections
  resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
  resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
  //如果用户没有配置对应的TypeHandler,则mybatis会推断具体的类型拦截器
  resolveTypeHandler();
  //各种校验
  validate();
  return resultMapping;
}

validate()方法的定义如下,跟我写的代码一样拉胯,就不做分析了:

mybatis源码学习------resultMap和sql片段的解析

解析 <discriminator>

官网中对于鉴别器的作用的描述如下:

有时候,一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。 鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。

解析discriminator的方法为processDiscriminatorElement方法

mybatis在解析discriminator时会创建一个Map来保存其各个case所对应的resultMap对象,示意图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JqzK9U8W-1604318520352)(/Users/lijiaxing/github/mybatis-3-master/doc/build/解析discriminator的示意图.png)]

  //解析鉴别器配置
  private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) {
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String typeHandler = context.getStringAttribute("typeHandler");
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    Map<String, String> discriminatorMap = new HashMap<>();
    for (XNode caseChild : context.getChildren()) {
      String value = caseChild.getStringAttribute("value");
      //处理嵌套结果映射
      String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings, resultType));
      discriminatorMap.put(value, resultMap);
    }
    //调用MapperBuilderAssistant#buildDiscriminator方法来构建Discriminatorshili
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
  }

processNestedResultMappings方法

该方法会创建鉴别器map集合中value所指定的resultMap对象

//处理嵌套结果映射
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) {
  if (Arrays.asList("association", "collection", "case").contains(context.getName())
    && context.getStringAttribute("select") == null) {
    //校验相关规则
    validateCollection(context, enclosingType);
    //校验通过后解析对应的resultMap节点
    ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
    return resultMap.getId();
  }
  return null;
}

MapperBuilderAssistant#buildDiscriminator

public Discriminator buildDiscriminator(
    Class<?> resultType,
    String column,
    Class<?> javaType,
    JdbcType jdbcType,
    Class<? extends TypeHandler<?>> typeHandler,
    Map<String, String> discriminatorMap) {
  //构建ResultMapping对象
  ResultMapping resultMapping = buildResultMapping(
      resultType,
      null,
      column,
      javaType,
      jdbcType,
      null,
      null,
      null,
      null,
      typeHandler,
      new ArrayList<>(),
      null,
      null,
      false);
  Map<String, String> namespaceDiscriminatorMap = new HashMap<>();
  //遍历map的value,为每一个resultMap的id加上namespace
  for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
    String resultMap = e.getValue();
    resultMap = applyCurrentNamespace(resultMap, true);
    namespaceDiscriminatorMap.put(e.getKey(), resultMap);
  }
  //通过建造者模式创建Discriminator对象
  return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
}

解析其他标签

处理其他情况,如<id><result><association>等标签的代码如下,调用buildResultMappingFromContext方法创建对应的ResultMapping对象,并将其保存在对应的list中即可。

mybatis源码学习------resultMap和sql片段的解析

创建并保存ResultMap对象

回顾resultMapElement方法的逻辑,将配置解析为ResultMapping对象并添加到集合中后,接下来要做的就是创建对应的ResultMap对象,并保存在configuration中。

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
  //省略
  
  //获取resultMap节点配置的ID
  //getValueBasedIdentifier()方法返回的格式为    类型1[ID1]_类型2[ID2]......
  //用于处理resultMap嵌套时,内层resultMap没有id的情况
  String id = resultMapNode.getStringAttribute("id",
    resultMapNode.getValueBasedIdentifier());
  //获取resultMap节点配置的继承关系
  String extend = resultMapNode.getStringAttribute("extends");
  //获取是否开启自动映射配置
  Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
  //创建ResultMap解析器实例
  ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
  try {
    //将具体的解析任务委派给MapperBuilderAssistant的实例
    return resultMapResolver.resolve();
  } catch (IncompleteElementException e) {
    configuration.addIncompleteResultMap(resultMapResolver);
    throw e;
  }
}

由上面的逻辑可知,XmlMapperBuilder在这里创建了一个ResultMapResolver对象,并通过该对象来完成ResultMap对象创建和保存工作。

ResultMapResolver类的定义

public class ResultMapResolver {
  private final MapperBuilderAssistant assistant;
  private final String id;
  private final Class<?> type;
  private final String extend;
  private final Discriminator discriminator;
  private final List<ResultMapping> resultMappings;
  private final Boolean autoMapping;

  public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
    this.assistant = assistant;
    this.id = id;
    this.type = type;
    this.extend = extend;
    this.discriminator = discriminator;
    this.resultMappings = resultMappings;
    this.autoMapping = autoMapping;
  }

  public ResultMap resolve() {
    //委派给MapperBuilderAssistant实例完成创建和保存的工作
    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
  }
}

MapperBuilderAssistant#addResultMap

public ResultMap addResultMap(
    String id,
    Class<?> type,
    String extend,
    Discriminator discriminator,
    List<ResultMapping> resultMappings,
    Boolean autoMapping) {
  //将ID替换为带有命名空间的格式
  id = applyCurrentNamespace(id, false);
  //将当前resultMap所继承的resultMap的ID替换为带有命名空间的格式
  extend = applyCurrentNamespace(extend, true);
  //处理继承树
  if (extend != null) {
    if (!configuration.hasResultMap(extend)) {
      throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
    }
    ResultMap resultMap = configuration.getResultMap(extend);
    //获取父节点中配置的所有ResultMapping对象
    List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
    //移除重复的ResultMapping配置
    extendedResultMappings.removeAll(resultMappings);
    //如果子resultMap节点中声明了<constructor><constructor/>标签,则需要将父节点中的构造器标签配置删除
    boolean declaresConstructor = false;
    for (ResultMapping resultMapping : resultMappings) {
      if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
        declaresConstructor = true;
        break;
      }
    }
    if (declaresConstructor) {
      //移除父节点中的构造器配置
      extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
    }
    //合并父子<resultMap>节点的ResultMapping配置
    resultMappings.addAll(extendedResultMappings);
  }
  //通过建造者设计模式创建ResultMap对象,并将其添加到mybatis的全局配置对象中
  ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
      .discriminator(discriminator)
      .build();
  configuration.addResultMap(resultMap);
  return resultMap;
}

Configuration#addResultMap

后面两个方法暂时没看懂是什么意思,先挖个坑,后面再来填

public void addResultMap(ResultMap rm) {
  resultMaps.put(rm.getId(), rm);
  //TODO
  checkLocallyForDiscriminatedNestedResultMaps(rm);
  //TODO
  checkGloballyForDiscriminatedNestedResultMaps(rm);
}

sql片段的解析

解析sql片段的代码入口为XmlMapperBuilder#sqlElement
mybatis源码学习------resultMap和sql片段的解析

sqlElement

private void sqlElement(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    sqlElement(list, configuration.getDatabaseId());
  }
  sqlElement(list, null);
}
//如果用户配置了databaseId,则会检测当前databaseId与configuration中配置的databaseId是否一致,
//一致则添加到sqlFragments属性中
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    String databaseId = context.getStringAttribute("databaseId");
    String id = context.getStringAttribute("id");
    //给id拼接namespace
    id = builderAssistant.applyCurrentNamespace(id, false);
    if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
      //如果匹配则将其添加到sqlFragments字段中
      sqlFragments.put(id, context);
    }
  }
}

databaseIdMatchesCurrent

//数据库厂商标识是否与当前sql片段的定义相同
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
  //如果需要的数据库厂商ID不为空
  if (requiredDatabaseId != null) {
    return requiredDatabaseId.equals(databaseId);
  }
  //隐含条件为需要的数据库厂商ID为空
  if (databaseId != null) {
    return false;
  }
  //如果sqlFragments集合中不存在对应的sql片段定义
  if (!this.sqlFragments.containsKey(id)) {
    return true;
  }
  //如果存在ID对应的sql片段
  XNode context = this.sqlFragments.get(id);
  //根据上面的if语句可以得出以下,如果程序能走到这里,则requiredDatabaseId一定为null,故直接判断databaseId属性的值是否为null即可
  return context.getStringAttribute("databaseId") == null;
}