MyBatis源码通~映射文件解析

Mapper映射文件解析

关联类:

  • XMLMapperBuilder
  • XMLStatementBuilder
  • MappedStatement

0、入口:XMLConfigBuilder.mapperElement(…)

1、 XMLMapperBuilder

  • 两种配置方式:

    • 配置package,会遍历该包下所有的类
    • 指定mapper文件的路径resource/url/class
  • 解析过程(以常用的配置xml文件路径为列):

    • 具体解析逻辑通过XMLMapperBuilder类来完成,解析xml文件中的<mapper>节点
      • cacheRefElement()/cacheElement():解析缓存cache-ref和cache(二级缓存配置)
      • 解析/mapper/parameterMap
      • resultMapElement():解析/mapper/resultMap
      • sqlElement():解析/mapper/sql
      • buildStatementFromContext():从“select|insert|update|delete”配置语句构建Statement

时序图

2、resultMapElement(建造者模式)

数据库字段与Java对象属性字段之间的映射
两个关联的类:ResultMappingResultMap

  • ResultMapping:记录数据字段一列与Java对象中的一个属性之间建立的映射关系。
  • ResultMap:每个节点都会被解析成一个ResultMap对象,其中每个节点所定义的映射关系,则用ResultMapping对象来记录。
    在这里插入图片描述
  • 最终节点数据会以:key=currentNamespace+"."+id;value=ResultMap保存到Configuration的resultMaps中。

2.1、<resultMap>节点解析

每个<resultMap>节点会被解析成一个ResultMap对象,<resultMap>节点中每一行属性配置会被解析成一个ResultMapping对象。多个ResultMapping构成了一个ResultMap对象。

//☆☆--XMLMapperBuilder: 构建ResultMap
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
//获取id和type
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));

    //NOTE: 解析type,表示一个JavaBean的完全限定名或者一个类型别名
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    
    //NOTE: 遍历解析<resultMap>里所有的行,一行配置用一个ResultMapping来记录
    List<ResultMapping> resultMappings = new ArrayList<>();
    resultMappings.addAll(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 {
        //NOTE: 基本类型的字段
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        
// 解析id 和property节点,并生成相应的ResultMapping对象
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    
    //NOTE: <resultMap>的id属性值,也将作为记录到Configuration中resultMaps的一个key
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    
    //通过ResultMapResolver构建ResultMap对象
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
    //调用resultMapResolver.resolve方法构建ResultMap对象
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
    //如果抛异常IncompleteElementException,则将resultMapResolver放入incompleteResultMaps集合中
    configuration.addIncompleteResultMap(resultMapResolver);
    throw e;
    }
  }
  • 解析每一行,构建ResultMapping对象
//解析id 和property节点,并生成相应的ResultMapping对象
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String property;
    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");
    /**
     * NOTE: 解析resultMap属性中出现<association/>和<collection/>节点,表示有嵌套内容,需通过processNestedResultMappings作进一步解析
     */
    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"));

    /**
     * NOTE: 解析javaType、typeHandler的类型以及枚举类型JdbcType
     */
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // MapperBuilderAssistant的buildResultMapping方法
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }

2.2、构建ResultMap

通过ResultMapResolver封装<resultMap>节点解析后的信息,并完成ResultMap对象的构建。

//XMLMapperBuilder
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
resultMapResolver.resolve();

//ResultMapResolver
 public ResultMap resolve() {
    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}

3、buildStatementFromContext

对每条<select />、<insert />、<update />、<delete /> 等标签,通过XMLStatementBuilder来构建MappedStatement。

3.1、 关联的类

XMLStatementBuilder、XMLIncludeTransformer、XMLScriptBuilder、SqlSourceBuilder、SqlSource、MappedStatement

3.2、XMLStatementBuilder

  • 解析<include>,将include替换成<sql>;
  • 对于${key}这种从属性配置文件中获取值的进行解析替换。
    • GenericTokenParser
  • 解析SQL节点,将定义的SQL节点信息构建成MappedStatement对象。
3.2.1、MappedStatement

记录SQL节点信息,包含了很多属性,平时常见的属性有:

  private String id;
  private List<ResultMap> resultMaps;
  private boolean useCache;
  /**
   * 记录缓存对象(二级缓存)
   */
  private Cache cache;
   /**
   * sql片段集
   */
  private SqlSource sqlSource;
  private KeyGenerator keyGenerator;

除这些还有SqlCommandType、StatementType等等。

3.2.2、开启SQL节点解析
  • 入口:parseStatementNode
public void parseStatementNode() {
   //NOTE. 节点id属性,对应Mapper接口中的方法名
    String id = context.getStringAttribute("id");
    //指定databaseId
    String databaseId = context.getStringAttribute("databaseId");
    
    //NOTE. 获取对应的SqlCommandType(select、insert、update、delete)
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    
    //NOTE: 是否刷新缓存
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    
    //NOTE: 是否使用二级缓存(select 语句默认使用缓存)
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    //NOTE: <include>语句解析
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());
    
    //NOTE: 解析parameterType(如传入一个对象)
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    
    //NOTE:一般不会配置,使用默认的XMLLanguagerDriver
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    //NOTE: 解析<update/>等操作内部 的<SelectKey>内置节点
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    //NOTE: 定义MappedStatement的id,区别于其他SQL节点
    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;
    }

    //NOTE: 创建SqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    
    //NOTE: Statement 默认PreparedStatement(有缓存)
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
   //NOTE: resultType
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    
    //NOTE: resultMap
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = 
    //NOTE: resultSetType,定义结果集的"滚动"模式
    context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    //NOTE:update语句相关属性
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    
    String resultSets = context.getStringAttribute("resultSets");
    
    //构建MappedStatement对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
3.2.3、XMLIncludeTransformer: <include/>节点解析

解析<include>节点,该过程会将其替换成<sql>节点中定义的SQL片段,并将其中的”${xxx}“占位符替换为真实的参数,主要过程在XMLIncludeTransformer.applyIncludes方法内完成。

3.2.3.1

-------TODO

3.3、SqlSourceBuilder创建 SqlSource

见SqlSource篇

SqlSourceBuilder、SqlSource、XMLScriptBuilder:构建动态SQLSqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

3.4、构建MappedStatement对象

在这里插入图片描述

  • MappedStatement
    • 映射的语句,每个<select />、<insert />、<update />、<delete /> 对应一个 MappedStatement 对象。
    • 最后以key=currentNamespce+"."+id属性值value=MappedStatement记录在Configuration中的mappedStatements中。
    • 关联的方法1:getStatementResultMaps()
      • 分为两种情况:配置了resultMap时,优先以resultMap为主
      • 当配置resultMap时,会根据resultMap的全限定名曲Configuration中找之前已经解析并记录好的数据。
      • 当未配置resultMap且配置了resultType时,会根据resultType构建一个-inline的ResultMap对象。
    • 关联的方法2:getBoundSql()
  • 创建MappedStatement,记录到Configuration的mappedStatements中
  • key=currentNamespace+"."+id; value=MappedStatement。
//--☆☆--MapperBuilderAssistant
public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      ......
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean useCache,
      ......
      LanguageDriver lang) {
      
    //等着缓存配置解析完成(配置了cacheRef)
    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

//调用MappedStatement 内部静态类构建MappedStatement对象
    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)
        //通过resultMap的名称、resultType解析出ResultMap对象
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);
    .......
    
    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
}
3.4.1、getStatementResultMaps
  • 获得 ResultMap 集合。
private List<ResultMap> getStatementResultMaps(String resultMap, Class<?> resultType, String statementId) {
//NOTE: 补全resultMap的全限定名
 resultMap = applyCurrentNamespace(resultMap, true);

List<ResultMap> resultMaps = new ArrayList<>();
 //NOTE: 有配置resultMap属性,配置多个时,以','分开,并从configuration中找到对应的ResultMap
    if (resultMap != null) {
      String[] resultMapNames = resultMap.split(",");
      for (String resultMapName : resultMapNames) {
        try {
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
        } catch (IllegalArgumentException e) {
          ......
        }
      }
    } else if (resultType != null) {
    //配置resultType,构建“statementId + "-Inline"为id的ResultMap
      ResultMap inlineResultMap = new ResultMap.Builder(configuration, statementId + "-Inline", resultType, new ArrayList<>(), null).build();
      resultMaps.add(inlineResultMap);
    }
    return resultMaps;
}
3.4.2、MappedStatement.build()

校验配置有效性

assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
assert mappedStatement.sqlSource != null;
assert mappedStatement.lang != null;

3.4.3、保存MappedStatement

记录MappedStatement到Configuration的mappedStatements Map中。

//记录MappedStatement到缓存中
configuration.addMappedStatement(statement);

//☆☆--Configuration: mappedStatements记录所有配置的MappedStatement
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

//☆☆--Configuration
protected static class StrictMap<V> extends HashMap<String, V> {
    private static final long serialVersionUID = -4950446264854982944L;
    private final String name;
    private BiFunction<V, V, String> conflictMessageProducer;

    public StrictMap(String name) {
      super();
      this.name = name;
    }

    /**
     * Assign a function for producing a conflict error message when contains value with the same key.
     * 若存在相同的key 时,返回错误信息
     */
    public StrictMap<V> conflictMessageProducer(BiFunction<V, V, String> conflictMessageProducer) {
      this.conflictMessageProducer = conflictMessageProducer;
      return this;
    }

    @SuppressWarnings("unchecked")
    public V put(String key, V value) {
      //NOTE: 若key已经存在则会保错
      if (containsKey(key)) {
        throw new IllegalArgumentException(name + " already contains value for " + key
            + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
      }
      //NOTE: 将key按照"."拆分,取数组最后一个string为key
      if (key.contains(".")) {
        final String shortKey = getShortName(key);
        if (super.get(shortKey) == null) {
          //不存在则调用HashMap原生的方法进行保存
          super.put(shortKey, value);
        } else {
          //NOTE: 若已经存在,则封装成Ambiguity再保存(存在的情况为:包路径不同,但Mapper接口名称一样): shortKey存在二义性
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      return super.put(key, value);
    }
   ......
  }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只打杂的码农

你的鼓励是对我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值