0
点赞
收藏
分享

微信扫一扫

队列的概念及使用

龙毓七七 03-03 20:30 阅读 2
mybatisjava
3.2.2.6 sqlSource生成

先看看 SqlSource 是什么,从源码看,它就是一个接口类,只有一个方法,获取 BoundSql ,BoundSql 是真正包装sql的类,里面有原 sql 字符以及对应的参数信息。

public interface SqlSource {
  BoundSql getBoundSql(Object parameterObject);
}

public class BoundSql {
  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Object parameterObject;
  private final Map<String, Object> additionalParameters;
  private final MetaObject metaParameters;
}

章节3.2.2.5中提到,生成SqlSource需要 langDriver,由上文得知,默认的 langDriver 是 XMLLanguageDriver,那么我们进入该类中创建SqlSource的方法

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    // 结合前面的内容,发现有很多地方用到Builder,看图`3-1`,它们的基类都是BaseBuilder
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    // 开始解析,继续往里探
    return builder.parseScriptNode();
}

图3-1

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

走进**XMLScriptBuilder 类的 parseScriptNode **方法

private void initNodeHandlerMap() {
    // 先看这个方法,看到下面的key是不是很熟悉,写sql时,经常会用到,sql中包含这些标签的,都会被当作 Dynamic 的 sqlSource,需要继续解析,还有${}包装的条件语句,也会被当作 Dynamic 的
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
}

public SqlSource parseScriptNode() {
    // 解析 Dynamic 的 sqlSource,最终拿到半成品 MixedSqlNode(组合sql节点)
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    // 是否 Dynamic,在 initNodeHandlerMap 方法中已经介绍过了
    if (isDynamic) {
        // 这一步就是创建了 DynamicSqlSource,并且 DynamicSqlSource 内部也没有做什么,就是对 configuration, rootSqlNode 两个赋值
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        // parameterType 是上面调用链一路带过来的,就是标签 parameterType 中设置的字符器对应的 Class,继续往里探,看后面`RawSqlSource 的创建`描述
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}

protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<>();
    // 拿到并遍历子节点
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
        XNode child = node.newXNode(children.item(i));
        if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
            // 文本节点,也就是非<foreach>、<choose>等的标签节点
            String data = child.getStringBody("");
            TextSqlNode textSqlNode = new TextSqlNode(data);
            // 这里主要是判断语句是不是包含${},如果有,则也定义为 Dynamic,并放到contents集合中,作下一步处理
            if (textSqlNode.isDynamic()) {
                contents.add(textSqlNode);
                isDynamic = true;
            } else {
                // Static 的,也存放到contents集合中
                contents.add(new StaticTextSqlNode(data));
            }
        } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
            // <foreach>、<choose>、<where>等的标签节点
            String nodeName = child.getNode().getNodeName();
            // 取出 initNodeHandlerMap 方法中定义的 nodeName 对应的 handler,这里以<where>标签为例
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            if (handler == null) {
                throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            
            handler.handleNode(child, contents);
            isDynamic = true;
        }
    }
    return new MixedSqlNode(contents);
}

// <where>标签的继续解析
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    // 又回到parseDynamicTags方法
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    // 包装成 WhereSqlNode,也存放到contents集合中
    WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
    targetContents.add(where);
}

RawSqlSource 的创建

public class RawSqlSource implements SqlSource {

    private final SqlSource sqlSource;

    public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
        this(configuration, getSql(configuration, rootSqlNode), parameterType);
    }

    public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
        // 构建 SqlSourceBuilder 对象
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        // 参数类型
        Class<?> clazz = parameterType == null ? Object.class : parameterType;
        // 将sql解析后封装成sqlSource,继续下探,看`关联1`
        sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
    }

    private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
        // 创建 DynamicContext 对象,DynamicContext 可以理解成拼接一个完整sql的过程类,所以叫context上下文,内部主要作用就是拼接sql和获取sql
        DynamicContext context = new DynamicContext(configuration, null);
        // rootSqlNode 此时应该是 MixedSqlNode,继续下探
        rootSqlNode.apply(context);
        // 再取出更完整的半成品sql,供下一步使用
        return context.getSql();
    }

    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        return sqlSource.getBoundSql(parameterObject);
    }

}

public boolean apply(DynamicContext context) {
    // 遍历List,此时node对应的是 StaticTextSqlNode,继续下探
    contents.forEach(node -> node.apply(context));
    return true;
}

public class StaticTextSqlNode implements SqlNode {
    private final String text;

    public StaticTextSqlNode(String text) {
        this.text = text;
    }

    @Override
    public boolean apply(DynamicContext context) {
        // 通过 DynamicContext 将sql拼接起来,内部其实就是一个StringBuilder在工作
        context.appendSql(text);
        return true;
    }

}

// 关联1
public class SqlSourceBuilder extends BaseBuilder {

    private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";

    public SqlSourceBuilder(Configuration configuration) {
        super(configuration);
    }
    // 这个方法其实就是把#{}包起来的部分全部解析成jdbc时PreparedStatement表示语句时的参数,用?来表示
    public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        // 有兴趣的读者可以去里面看看具体解析过程,就是对字符串中的字符一个个遍历判断
        String sql = parser.parse(originalSql);
        // 最终封装成 StaticSqlSource 返回,handler.getParameterMappings() 这个要解释一下,后续做参数赋值时需要用到,也就是维护#{}里面的参数名、类型等信息,后续设置参数值时会根据名称和类型来处理
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }
}

sqlSource生成后,最终还要创建MappedStatement,前者只是把mapper中的sql都解析出来了,后者是要把整个sql相关的全部串起来,包括参数值的设置、缓存、keyGenerator、结果集等,最终执行器执行时,都跟这有关。那么,接下来看看如何生成MappedStatement,先看看 MappedStatement 有什么,能做什么。

// 部分代码省略了,比如get/set方法
public final class MappedStatement {
  // 所对应的mapper文件地址,比如xxxx/xx/xx/xxMapper.xml
  private String resource;
  // 全局配置信息维护类
  private Configuration configuration;
  // id 也就是命名空间
  private String id;
  // 下面的这些属性值在前面的章节都有介绍,这里就不赘述了
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  private SqlSource sqlSource;
  private Cache cache;
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;
  // 这个介绍下,简单点,就把它想成类似主键值的生成的行为
  private KeyGenerator keyGenerator;
  // keyProperty : selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称
  private String[] keyProperties;
  // keyColumn	返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  // resultSets	这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。一般用在存储过程调用
  private String[] resultSets;

  MappedStatement() {
    // constructor disabled
  }

  public static class Builder {
    // 创建 MappedStatement 对象
    private MappedStatement mappedStatement = new MappedStatement();

    public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
      // 根据传参设置 MappedStatement 对象属性值
      mappedStatement.configuration = configuration;
      mappedStatement.id = id;
      mappedStatement.sqlSource = sqlSource;
      mappedStatement.statementType = StatementType.PREPARED;
      mappedStatement.resultSetType = ResultSetType.DEFAULT;
      mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<>()).build();
      mappedStatement.resultMaps = new ArrayList<>();
      mappedStatement.sqlCommandType = sqlCommandType;
      mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      String logId = id;
      if (configuration.getLogPrefix() != null) {
        logId = configuration.getLogPrefix() + id;
      }
      mappedStatement.statementLog = LogFactory.getLog(logId);
      mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
    }

   
  }


  public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

  private static String[] delimitedStringToArray(String in) {
    if (in == null || in.trim().length() == 0) {
      return null;
    } else {
      return in.split(",");
    }
  }

}

介绍完 MappedStatement 后,现在从创建它的入口开始介绍,如何创建它,继续往下看,看 MapperBuilderAssistant.addMappedStatement 方法

public MappedStatement addMappedStatement(
    String id,
    SqlSource sqlSource,
    StatementType statementType,
    SqlCommandType sqlCommandType,
    Integer fetchSize,
    Integer timeout,
    String parameterMap,
    Class<?> parameterType,
    String resultMap,
    Class<?> resultType,
    ResultSetType resultSetType,
    boolean flushCache,
    boolean useCache,
    boolean resultOrdered,
    KeyGenerator keyGenerator,
    String keyProperty,
    String keyColumn,
    String databaseId,
    LanguageDriver lang,
    String resultSets) {

    if (unresolvedCacheRef) {
        throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 这一步就是通过 MappedStatement.Builder 创建 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)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);
    // 获取参数映射数据,看`关联1`
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
        // 将 parameterMap 赋值到 mappedStatement 中,后面有用
        statementBuilder.parameterMap(statementParameterMap);
    }
    // 这一步也没做什么,就是把 mappedStatement.resultMaps 的List,设置为不可修改的List
    MappedStatement statement = statementBuilder.build();
    // 将 MappedStatement 对象,添加到 configuration 对应的 HashMap中,key = 语句id(命名空间+标签语句定义的 id),value = statement
    configuration.addMappedStatement(statement);
    return statement;
}
// 关联1
private ParameterMap getStatementParameterMap(
    String parameterMapName,
    Class<?> parameterTypeClass,
    String statementId) {
    parameterMapName = applyCurrentNamespace(parameterMapName, true);
    ParameterMap parameterMap = null;
    // 这一块是对 parameterMap 的处理,前面已经讲了,这个属性已经被废弃了,就不浪费篇章来讲了
    if (parameterMapName != null) {
        try {
            parameterMap = configuration.getParameterMap(parameterMapName);
        } catch (IllegalArgumentException e) {
            throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
        }
    } else if (parameterTypeClass != null) {
        // 用 parameterType 设置的值的 Class 来构建一个 parameterMap,此时还没有对参数进行赋值处理
        List<ParameterMapping> parameterMappings = new ArrayList<>();
        parameterMap = new ParameterMap.Builder(
            configuration,
            statementId + "-Inline",
            parameterTypeClass,
            parameterMappings).build();
    }
    return parameterMap;
}

到此,MappedStatement 的创建已经完成了,为执行sql前的准备,全部做足了。

后续将通过抖音视频/直播的形式分享技术,由于前期要做一些准备和规划,预计2024年6月开始,欢迎关注,如有需要或问题咨询,也可直接抖音沟通交流。
在这里插入图片描述

举报

相关推荐

0 条评论