《Mybatis源码》第14章 LanguageDriver

总结

  1. LanguageDriver借助XMLScriptBuilder解析标签,创建SqlSource
  2. XMLScriptBuilder负责解析XML标签,也就是这里负责解析,封装成SqlNode,然后在调用的时候,再根据参数解析,拼接成最后的SQL

引入

在执行器执行query()方法的时候,会直接在MappedStatement里面获取BoundSql

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 直接从MappedStatement获取BoundSql
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

接下来进入到MappedStatement里面的getBoundSql()

public BoundSql getBoundSql(Object parameterObject) {
    // 获取BoundSql  这个流程在之前讲过,中间会经历 动态编译/静态编译 在加上参数映射处理
    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;
  }

上面看到我们之前使用的sqlSource是直接存在的,然后会进行编译,那么就代表我们解析完DML标签,生成MapperStatement的时候,SqlSource就已经创建好了,那么接下来就来看一下如何创建的SqlSource,以及如何把SQL语句变成对应的SqlNode

接下来又回来了XMLStatementBuilderparseStatementNode(),在其中会获取DML标签的各种属性,然后创建MapperStatement,其中SqlSource就是借助LanguageDriver来解析的

public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    // 1.获取到语言驱动
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    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
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    // 2.创建SqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    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;
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

  // 获取语言驱动
  private LanguageDriver getLanguageDriver(String lang) {
    Class<? extends LanguageDriver> langClass = null;
    if (lang != null) {
      langClass = resolveClass(lang);
    }
    // lang一般为空 所以直接会获取
    return configuration.getLanguageDriver(langClass);
  }

上面的代码中会经历两步,

  1. 通过lang属性来配置语言驱动,但是我在官网上并没有找到关于该属性的配置,可以推测,该属性已经不建议使用,但是代码还保留,我们用的时候肯定直接创建默认的
  2. 通过langDriver来创建SqlSource

LanguageDriver

public interface LanguageDriver {

  // 创建参数映射
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);

  // 创建数据源
  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);

  // 创建数据源
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);

}

LanguageDriverRegistry 驱动注册

用于语言驱动注册,提供了一些简答的方法,包括注册、获取等,内容很简单

public class LanguageDriverRegistry {
  
  // 注册的语言驱动Map
  // key为Class value为对象
  private final Map<Class<? extends LanguageDriver>, LanguageDriver> LANGUAGE_DRIVER_MAP = new HashMap<>();
  // 默认的语言驱动 默认为XMLLanguageDriver
  private Class<? extends LanguageDriver> defaultDriverClass;

  // 注册  已有class 根据反射实例化 然后存入Map
  public void register(Class<? extends LanguageDriver> cls) {
    if (cls == null) {
      throw new IllegalArgumentException("null is not a valid Language Driver");
    }
    MapUtil.computeIfAbsent(LANGUAGE_DRIVER_MAP, cls, k -> {
      try {
        return k.getDeclaredConstructor().newInstance();
      } catch (Exception ex) {
        throw new ScriptingException("Failed to load language driver for " + cls.getName(), ex);
      }
    });
  }
  // 注册 已有实体对象 获取class 然后存入Map
  public void register(LanguageDriver instance) {
    if (instance == null) {
      throw new IllegalArgumentException("null is not a valid Language Driver");
    }
    Class<? extends LanguageDriver> cls = instance.getClass();
    if (!LANGUAGE_DRIVER_MAP.containsKey(cls)) {
      LANGUAGE_DRIVER_MAP.put(cls, instance);
    }
  }
  
  public LanguageDriver getDriver(Class<? extends LanguageDriver> cls) {
    return LANGUAGE_DRIVER_MAP.get(cls);
  }

  public LanguageDriver getDefaultDriver() {
    return getDriver(getDefaultDriverClass());
  }

  public Class<? extends LanguageDriver> getDefaultDriverClass() {
    return defaultDriverClass;
  }

  public void setDefaultDriverClass(Class<? extends LanguageDriver> defaultDriverClass) {
    register(defaultDriverClass);
    this.defaultDriverClass = defaultDriverClass;
  }
}

初始化

我们的LanguageDriver 只有2个实现字类,那么注册类里面的Map最多也只有2个,那么它在哪里初始化的呢 答案在Configuration的构造方法里面

public Configuration() {
    // 上面省略一些代码...

    // 默认语言驱动为 XMLLanguageDriver
    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    // 注册另一个驱动
    languageRegistry.register(RawLanguageDriver.class);
  }

XMLLanguageDriver 动态SQL语言驱动

// Mybatis默认XML驱动类为XMLLanguageDriver
// 其主要作用于解析select|update|insert|delete节点为完整的SQL语句
public class XMLLanguageDriver implements LanguageDriver {

  @Override
  public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    // 创建默认的DefaultParameterHandler
    // 我们之前使用的参数处理器 都是在这里创建返回的
    return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
  }
  
  // 我们在 XMLStatementBuilder 里面创建SqlSource的时候,会调用该方法
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    // 这里创建了建造类
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    //  当sql中包含有${} 或者 动态SQL标签名 就认为是动态SQL
    //  如果是动态返回 {@link DynamicSqlSource} ,否则 {@link RawSqlSource}
    return builder.parseScriptNode();
  }
  
  // 上面的参数是XNode  这里面是String 需要先转成XNode
  @Override
  public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    // issue #3
    // 此处兼容XML方式的解析,条件以<script>为头结点
    if (script.startsWith("<script>")) {// 将内容转换成XNode 然后在创建
      XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
      return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
    } else {
      // issue #127
      // 解析Configuration#variable变量,将有${...}形式的字符串转换成对应字符串
      script = PropertyParser.parse(script, configuration.getVariables());
      // 根据TextSqlNode的内部属性isDynamic来进行解析帮助类的分配
      TextSqlNode textSqlNode = new TextSqlNode(script);
      if (textSqlNode.isDynamic()) {
        return new DynamicSqlSource(configuration, textSqlNode);
      } else {
        return new RawSqlSource(configuration, script, parameterType);
      }
    }
  }

}

RawLanguageDriver 静态SQL语句驱动

默认XML语言是能够识别静态语句并创建一个{@link RawSqlSource},所以没有需要使用原始的,除非你想确保没有任何动态标记任何理由。

所以该类的使用频率比较少

// 继承了XMLLanguageDriver
public class RawLanguageDriver extends XMLLanguageDriver {

  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    SqlSource source = super.createSqlSource(configuration, script, parameterType);
    checkIsNotDynamic(source);
    return source;
  }

  @Override
  public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    SqlSource source = super.createSqlSource(configuration, script, parameterType);
    checkIsNotDynamic(source);
    return source;
  }

  private void checkIsNotDynamic(SqlSource source) {
    if (!RawSqlSource.class.equals(source.getClass())) {
      throw new BuilderException("Dynamic content is not allowed when using RAW language");
    }
  }

}

XMLScriptBuilder XML解析

SqlSource 是基于XML解析而来,解析的底层是使用Dom4j 把XML解析成一个个子节点,在通过 XMLScriptBuilder 遍历这些子节点最后生成对应的SqlSource。其解析流程如下图:

从图中可以看出这是一种递归式的访问 所有节点,如果是文本节点就会直接创建TextNode 或StaticSqlNode。否则就会创建动态脚本节点如IfSqlNode等。这里每种动态节点都会对应的处理器(NodeHandler)来创建。创建好之后又会继续访问子节点,让递归继续下去。

public class XMLScriptBuilder extends BaseBuilder {
  
  // 整个的DML标签  包括标签名 内容  也就是未解析之前的内容
  private final XNode context;
  // 是否动态SQL
  private boolean isDynamic;
  // 参数类型
  private final Class<?> parameterType;
  // 节点处理Map
  private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();

  public XMLScriptBuilder(Configuration configuration, XNode context) {
    this(configuration, context, null);
  }

  public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    initNodeHandlerMap();
  }

  private void initNodeHandlerMap() {
    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() {
    // 转换动态标签
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    // 动态则创建 DynamicSqlSource
    // 静态则创建 RawSqlSource
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      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.newXNode(Node node):新建一个XNode,XNode只是mybatis对Node实例的封装,用于方便操作
      // 并不是添加node节点的功能
      XNode child = node.newXNode(children.item(i));
      /**
       * Node.CDATA_SECTION_NODE:代表文档中的 CDATA 部(不会由解析器解析的文本)
       * Node.TEXT_NODE:代表元素或属性中的文本内容
       */
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        // 文本数据
        String data = child.getStringBody("");
        // 创建TextSqlNode
        TextSqlNode textSqlNode = new TextSqlNode(data);
        // 判断是否为动态节点 也就是判断是否包含 ${} 
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          // 添加静态文本节点
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // Node.ELEMENT_NODE:代表元素
        // 获取到子节点名
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        // 处理节点
        handler.handleNode(child, contents);
        // 并且标记为动态SQL
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }
}

NodeHandler 标签节点处理器

private interface NodeHandler {
    // nodeToHandle 子节点
    // targetContents 目标节点
	void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}

BindHandler

private class BindHandler implements NodeHandler {
  public BindHandler() {
    // Prevent Synthetic Access
  }

  @Override
  public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    final String name = nodeToHandle.getStringAttribute("name");
    final String expression = nodeToHandle.getStringAttribute("value");
    final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
    targetContents.add(node);
  }
}

TrimHandler

 private class TrimHandler implements NodeHandler {
    public TrimHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      // 递归处理子节点
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String prefix = nodeToHandle.getStringAttribute("prefix");
      String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
      String suffix = nodeToHandle.getStringAttribute("suffix");
      String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
      // 创建后存入targetContents
      TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
      targetContents.add(trim);
    }
  }

WhereHandler

private class WhereHandler implements NodeHandler {
    public WhereHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      // 递归
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
      targetContents.add(where);
    }
  }

SetHandler

private class SetHandler implements NodeHandler {
    public SetHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      // 递归
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
      targetContents.add(set);
    }
  }

ForEachHandler

private class ForEachHandler implements NodeHandler {
    public ForEachHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String collection = nodeToHandle.getStringAttribute("collection");
      String item = nodeToHandle.getStringAttribute("item");
      String index = nodeToHandle.getStringAttribute("index");
      String open = nodeToHandle.getStringAttribute("open");
      String close = nodeToHandle.getStringAttribute("close");
      String separator = nodeToHandle.getStringAttribute("separator");
      ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
      targetContents.add(forEachSqlNode);
    }
  }

IfHandler

private class IfHandler implements NodeHandler {
    public IfHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }
  }

OtherwiseHandler

private class OtherwiseHandler implements NodeHandler {
    public OtherwiseHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      targetContents.add(mixedSqlNode);
    }
  }

ChooseHandler

private class ChooseHandler implements NodeHandler {
    public ChooseHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      List<SqlNode> whenSqlNodes = new ArrayList<>();
      List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
      handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
      SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
      ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
      targetContents.add(chooseSqlNode);
    }

    private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
      List<XNode> children = chooseSqlNode.getChildren();
      for (XNode child : children) {
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler instanceof IfHandler) {
          handler.handleNode(child, ifSqlNodes);
        } else if (handler instanceof OtherwiseHandler) {
          handler.handleNode(child, defaultSqlNodes);
        }
      }
    }

    private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
      SqlNode defaultSqlNode = null;
      if (defaultSqlNodes.size() == 1) {
        defaultSqlNode = defaultSqlNodes.get(0);
      } else if (defaultSqlNodes.size() > 1) {
        throw new BuilderException("Too many default (otherwise) elements in choose statement.");
      }
      return defaultSqlNode;
    }
  }

andleNode(child, ifSqlNodes);
} else if (handler instanceof OtherwiseHandler) {
handler.handleNode(child, defaultSqlNodes);
}
}
}

private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
  SqlNode defaultSqlNode = null;
  if (defaultSqlNodes.size() == 1) {
    defaultSqlNode = defaultSqlNodes.get(0);
  } else if (defaultSqlNodes.size() > 1) {
    throw new BuilderException("Too many default (otherwise) elements in choose statement.");
  }
  return defaultSqlNode;
}

}












评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

为人师表好少年

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值