Mybatis 源码探究 (4) 将sql 语句中的#{id} 替换成 ‘?

2022-10-31 14:40:46 浏览数 (1)

Mybatis 源码探究 (4) 将sql 语句中的#{id} 替换成 '?

出于好奇,然后就有了这篇文章啦。 源码给我的感觉,是一座大山的感觉。曲曲折折的路很多,点进去就有可能出不来。 不过慢慢看下来,收货是有的,对一些理解更为深刻了,而且越来越觉得数据结构是真的真的重要,底层的类,就没有不用到数据结构的。

传进来的参数textselect t_user.id,t_user.username,t_user.password from t_user where t_user.id=#{id}

这里需要做的就是讲#{id} 替换成 ?。

GenericTokenParser 类

代码语言:javascript复制
package org.apache.ibatis.parsing;

/**
 * @author Clinton Begin
 */
public class GenericTokenParser {

  private final String openToken;  //  openToken: "#{"
  private final String closeToken; // closeToken: "}"
  // 这里实际调用的是TokenHandler的实现类  SqlSourceBuilder类中的ParameterMappingTokenHandler
  private final TokenHandler handler; 
    
  public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }

  public String parse(String text) {
    if (text == null || text.isEmpty()) {
      return "";
    }
    // search open token
    // 这里就是找到 "#{" 的起始位置
    int start = text.indexOf(openToken);
    if (start == -1) {
      return text;
    }
    // 将text划分为 字符数组 
    char[] src = text.toCharArray();
    int offset = 0;
    //StringBuilder 文末有讲 可理解为 StringBuffer  
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    do {
      if (start > 0 && src[start - 1] == '\') {
        // this open token is escaped. remove the backslash and continue.
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start   openToken.length();
      } else {
        // found open token. let's search close token.
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        builder.append(src, offset, start - offset);
        //"select t_user.id,t_user.username,t_user.password from t_user where t_user.id="
        // 可以理解为 将去除了#{} 的sql 语句 重新赋值给 builder啦
        offset = start   openToken.length();// 定位到参数的开始位置 
        
        // 从  offset 索引开始搜索 "}" 出现的位置 赋给end 
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {
          if (end > offset && src[end - 1] == '\') {
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end   closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {
            // 取到"id"  然后添加进  expression
            expression.append(src, offset, end - offset);
            break;
          }
        }
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          // 此时 handler.handleToken(expression.toString()) 返回值 实际就是 "? "
          // 但之中还做了其他操作,我们暂不分析。
          builder.append(handler.handleToken(expression.toString()));
          offset = end   closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    } while (start > -1);
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    //此时 返回的已是: select t_user.id,t_user.username,t_user.password from t_user where t_user.id=?
    return builder.toString();
  }
}

handler.handleToken(expression.toString())

代码语言:javascript复制
@Override
public String handleToken(String content) {
    parameterMappings.add(buildParameterMapping(content));
    return "?";
}

在这个方法中,其返回值就是返回一个“?” 。buildParameterMapping(content) 做了一些操作,看起来像检验类型,我没有完全看懂,不乱说。 parameterMappings.add(); 这个 parameterMappings 实际上是一个 private List parameterMappings = new ArrayList<>(); List的数据结构 存储。这步操作肯定是有用的,但是我目前还没有明白哈。

StringBuilder

  • 一个可变的字符序列。 此类提供与StringBuffer兼容的 API,但不保证同步。 此类旨在用作StringBuffer替代品,用于在单个线程使用字符串缓冲区的地方(通常是这种情况)。 在可能的情况下,建议优先使用此类而不是StringBuffer因为在大多数实现下它会更快。
  • StringBuilder上的主要操作是append和insert方法,它们被重载以接受任何类型的数据。 每个都有效地将给定的数据转换为字符串,然后将该字符串的字符附加或插入到字符串构建器中。 append方法总是在构建器的末尾添加这些字符; insert方法在指定点添加字符。
  • 例如,如果z指字符串生成器对象,其当前内容是“ start ”,则该方法调用z.append(“le”)将导致字符串生成器含有“ startle ”,而z.insert(4, “le”)会将字符串生成器更改为包含“ starlet ”。
  • 通常,如果 sb 引用StringBuilder的实例,则sb.append(x)与sb.insert(sb.length(), x)具有相同的效果。 每个字符串生成器都有容量。 只要字符串生成器中包含的字符序列的长度不超过容量,就没有必要分配新的内部缓冲区。 如果内部缓冲区溢出,它会自动变大。
  • 多线程使用StringBuilder实例是不安全的。 如果需要此类同步,则建议使用StringBuffer 。

自言自语

虽然对于mybatis 仍然感觉什么都没有懂,都只是出于好奇,去探究一下。 但是在这个过程中,我深刻的感受到了数据结构的重要性,底层的存储不是Map 就是List 等等。 就像Mybatis 的一级缓存,二级缓存等等,他们的底层存储就是依赖于不同的数据结构的。

0 人点赞