学习链接
MyBatis SqlSource解析
【Mybatis】Mybatis源码之SqlSource#getBoundSql获取预编译SQL
Mybatis中SqlSource解析流程详解
Mybatis TypeHandler解析
图解
Mybatis的SqlSource&SqlNode - processon
DynamicSqlSource
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;
}
/**
* 获取一个BoundSql对象
*
* DynamicSqlSource和 RawSqlSource都会转化为 StaticSqlSource,然后才能给出一个 BoundSql对象。
*
* @param parameterObject 参数对象
* @return BoundSql对象
*/
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 创建DynamicSqlSource的辅助类,用来记录DynamicSqlSource解析出来的SQL片段信息和参数信息
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 这里会从根节点开始,对节点逐层调用apply方法,经过这一步后,动态节点"${}"都被替换,这样 DynamicSqlSource便不再是动态的,而是静态的。
rootSqlNode.apply(context);
// 处理占位符,汇总参数信息
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// 使用SqlSourceBuilder处理"#{}",将其转化为"?",然后创建ParameterMapping,最终生成了StaticSqlSource对象
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 把context.getBindings()的参数放到boundSql的metaParameters中进行保存
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
RawSqlSource
通过观察RawSqlSource与DynamicSqlSource的区别,可以注意到:
- 首先需要判定在mapper.xml中写的一个sql标签语句到底是动态sqlSource还是rawSqlSource?这个在XMLScriptBuilder#parseDynamicTags中有解析过程,凡是含有${} 或者 还有动态标签(如trim,where,if等9个标签)的 的都是动态的
- DynamicSqlSource和RawSqlSource有什么共同点和区别?它们都有解析阶段 和 运行阶段,在解析阶段时,RawSqlSource在构造方法中就已经得到了StaticSqlSource了(因为里面没有需要动态解析的内容了,注意#{}不属于动态解析的范畴,只有那9个动态标签和${}才算动态的),而DynamicSqlSource需要在运行阶段根据传参,才能获得StaticSqlSource,需要根据实际传参才能确定sql,所以叫动态嘛。所以它们最终都会获取StaticSqlSource,参与后面的流程。
public class RawSqlSource implements SqlSource {
private final SqlSource sqlSource;
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
}
}
GenericTokenParser#parse
/**
* 该方法主要 完成占位符的定位工作,然后把占位符的替换工作交给与其关联的 TokenHandler 处理
* @param text
* @return
*/
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
// 查找openToken的位置
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;
// 当存在openToken时,才继续处理
while (start > -1) {
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);
}
// 拼接从0到openToken之前的字符
builder.append(src, offset, start - offset);
// 设置offset值为openToken结束的位置
offset = start + openToken.length();
// 从offset值之后开始找第一个closeToken的位置
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();
// 继续查找当前closeToken之后的closeToken
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
break;
}
}
// 如果不存在
if (end == -1) {
// close token was not found.
// 拼接剩余的字符
builder.append(src, start, src.length - start);
// 设置offset为字符数组的长度
offset = src.length;
} else {
/**
* DynamicCheckerTokenParser:如果存在,则设置当前SQL为动态的
* BindingTokenParser:获取$变量的值
* ParameterMappingTokenHandler:将#替换为?,并构建参数映射ParameterMapping
*/
builder.append(handler.handleToken(expression.toString()));
// 设置offset值为closeToken结束的位置
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
// 拼接剩余的字符
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}