Mybatis动态解析sql

Mybatis动态解析sql

SqlNode

/**
 * SQL节点, 主要与SQL的解析相关
 */
public interface SqlNode {
  /**
   * 根据用户传入的参数解析记录的动态SQL语句, 并添加到 DynamicContext.sqlBuilder 中进行保存
   */
  boolean apply(DynamicContext context);
}

SqlNode接口,可以理解为xml中的每个标签,比如sql的update,trim,if标签。
在这里插入图片描述

SqlSource

public interface SqlSource {
  // 通过解析得到 BoundSql 对象, BoundSql 中封装了包含 “?”的占位符的SQL, 以及绑定的参数
  BoundSql getBoundSql(Object parameterObject);
}

代表从xml文件或注解映射的sql内容,主要就是用于创建BoundSql,有实现类DynamicSqlSource(动态Sql源),StaticSqlSource(静态Sql源)等
在这里插入图片描述

封装mybatis最终产生sql的类,包括sql语句,参数,参数源数据等参数:

/**
 * {@link SqlSource}处理完动态内容之后, 会生产实际的 SQL 语句 , 
 * 该语句中可能有占位符"?" 和 有序的参数映射,以及传入的参数
 */
public class BoundSql {
  // 进行 #{ } 和 ${ } 替换完毕之后的结果sql, 注意每个 #{ }替换完之后就是一个 ?
  private final String sql;
  // 参数映射列表, 与 sql 中的占位符有序, 一一对应
  private final List<ParameterMapping> parameterMappings;
  // 用户传入的参数
  private final Object parameterObject;
  private final Map<String, Object> additionalParameters;
  private final MetaObject metaParameters;
  public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.parameterObject = parameterObject;
    this.additionalParameters = new HashMap<>();
    this.metaParameters = configuration.newMetaObject(additionalParameters);
  }

LanguageDriver

public interface LanguageDriver {
  /**
   * 创建 ParameterHandler
   * @see DefaultParameterHandler
   */
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);

  /**
   * 创建 SqlSource 对象:从XML中读取 statetment
   * @return
   */
  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);

  /**
   * 创建 SqlSource 对象:从注释中读取 statetment
   * @return 
   */
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);

}

在这里插入图片描述

XMLLanguageDriver(处理xml中的sql,RawLanguageDriver处理静态sql):

XMLLanguageDriver内部会使用XMLScriptBuilder解析xml中的sql部分。

解析动态 sql

buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)); 这个方法是上篇文章最终解析sql的入口

  /**
   * 解析各个 SQL 语句
   * @param list
   * @param requiredDatabaseId
   */
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    // 遍历 XNode 节点
    for (XNode context : list) {
      // 建造者模式
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 解析
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        // 无法解析的添加到 Configuration 对象
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

 public void parseStatementNode() {
    // 获取 id 属性
    String id = context.getStringAttribute("id");
    // 获取 databaseid
    String databaseId = context.getStringAttribute("databaseId");
    //验证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");
    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
    // 引入include 解析出的 sql 节点内容
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    // 处理 selectKey
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
     //得到sqlSqlSource就可以得到BoundSql(这个就是最终的sql)
    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;
    }
     //最总put到mappedStatements里面
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }




//默认使用XMLLanguageDriver创建SqlSource
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }

  /**
   * 解析动态节点
   * @return
   */
  public SqlSource parseScriptNode() {
    // 首先判断是不是动态节点   如果是动态节点会递归解析
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

/**
 * @author Clinton Begin
 * 负责动态SQL的解析
 */
public class DynamicSqlSource implements SqlSource {

  private final Configuration configuration;
  private final SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    // 获取 DynamicContext 对象, parameterObject为用户传入的参数
    // sql语句解析,优先解析${}字符
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // 解析 context
    rootSqlNode.apply(context);
    // 创建 SqlSourceBuilder 对象
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    // 获取参数的类型
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    //创建StaticSqlSource,其也会去解析#{}字符。
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    // 获取 BoundSql 对象
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 将 DynamicContext.bindings 中的参数复制到 metaParameters 集合中
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }
    
    
 //RawSqlSource  静态sql的解析     new RawSqlSource(configuration, rootSqlNode, parameterType);
      /**
   * 构造函数
   */
  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    // 如果没有参数类型, 则将参数类型定义为 Object
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    // 返回的是 StaticSqlSource
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
  }
    
        /**
     * SQL的进一步处理
     * @param originalSql 经过SqlNode初步处理之后的SQL语句
     * @param parameterType 用户传入的参数类型
     * @param additionalParameters 记录形参与实参的对应关系
     * @return
     */
  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    // 创建通用标记处理器, 主要是使用其 handleToken 方法
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    // 创建对应的解析器, 里面调用了处理器
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    // 并将内容替换为 ?
    String sql = parser.parse(originalSql);
    // 返回一个 StaticSqlSource 对象
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

动态sql和静态sql解析对比

  • DynamicSqlSource解析含有 ${} 的sql语句,而RawSqlSource解析含有 #{} 的sql语句

  • DynamicSqlSource涵盖的操作比RawSqlSource多了一步,便是优先处理*${}*字符,其本身也会调用去解析 #{} 字符

  • *KaTeX parse error: Expected 'EOF', got '#' at position 60: …值,不做字符串引号拼装;而 *#̲{}* 语句的解析是最终转换为…{pwd}如果pwd参数传入aaa or 1=1,则上述拼装后的结果为select * from user where name=‘admin’ and pwd=aaa or 1=1。这个表达式会恒为真,直接会绕过验证,风险贼高。如果上述采用#{pwd},则传入aaa or 1=1,则最后的生成语句为select * from user=‘admin’ and pwd=‘aaa or 1=1’`。这个表达式验证通不过,有较高的安全性,防止sql恶意注入

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值