MyBatis源码通~SQL节点解析

XMLStatementBuilder

解析SQL节点,将定义的SQL节点信息构建成MappedStatement对象。

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等等。

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

2.1、XMLIncludeTransformer: <include/>节点解析

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

2.2、创建 SqlSource:见SqlSource篇

见SqlSource篇

2.3、构建MappedStatement对象

  • 创建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;
}
2.3.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;
}
2.3.2、MappedStatement.build()

校验配置有效性

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

2.4、记录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);
    }
   ......
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一只打杂的码农

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

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

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

打赏作者

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

抵扣说明:

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

余额充值