前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis 源码探究 (4) 将sql 语句中的#{id} 替换成 ‘?

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

作者头像
宁在春
发布2022-10-31 14:40:46
5480
发布2022-10-31 14:40:46
举报
文章被收录于专栏:关于Java学习@宁在春

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 的一级缓存,二级缓存等等,他们的底层存储就是依赖于不同的数据结构的。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-07-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Mybatis 源码探究 (4) 将sql 语句中的#{id} 替换成 '?
    • GenericTokenParser 类
      • handler.handleToken(expression.toString())
        • StringBuilder
          • 自言自语
          相关产品与服务
          对象存储
          对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档