我们的项目中通常写很多sql,而这种屡见不鲜的占位符却有很多人不知道它底层的实现,最近和一个朋友聊到这事,一时兴起,所以就撸了一会源码,希望借助我的理解,助你也搞清楚这块的知识!
废话不多说,直接上测试用例:
很简单的一串代码,但是内部逻辑是有点复杂
@Test
public void testMybatis(){
final Map<String,String> mapper = new HashMap<String, String>();
mapper.put("author", "威威");
mapper.put("code", "8888");
//初始化一个处理程序
TokenHandler handler = content -> mapper.get(content);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
System.out.println("看看这是啥: " + parser.parse("作者:#{author},您的工号是:#{code}"));
}
根据上图可以看得出,替换赋值是是用map中的value,而占位符里面的正是map中的key,这里我们要稍微思考下,究竟是怎么解析出我的key?
mybatis中有动态Sql源类:DynamicSqlSource
@Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
//设置参数信息
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
,跟进去里面的parse
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql;
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
这里开始解析占位符#{ }
下面直接看真正解析的代码:
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// 寻找#{的索引位置
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
//创建对象用来拼接字符串
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
do {
if (start > 0 && src[start - 1] == '\\') {
// 这个#{被转义。删除并继续
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// 接着搜索 }
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// 这个}被转义,删除并继续
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// 没有找到 }
builder.append(src, start, src.length - start);
offset = src.length;
} else {
//这里开始解析赋值
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);
}
return builder.toString();
}