Mybatis 源码探究 (4) 将sql 语句中的#{id} 替换成 '?
出于好奇,然后就有了这篇文章啦。 源码给我的感觉,是一座大山的感觉。曲曲折折的路很多,点进去就有可能出不来。 不过慢慢看下来,收货是有的,对一些理解更为深刻了,而且越来越觉得数据结构是真的真的重要,底层的类,就没有不用到数据结构的。
传进来的参数text
是 select 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 的一级缓存,二级缓存等等,他们的底层存储就是依赖于不同的数据结构的。