先给出一段代码,类似于下面的复杂SQL,在解析过程中又是怎么执行的,调用了哪些API
<select id="getAll" parameterType="map" resultType="map">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
select userId,username,age
from user
where 1=1
<if test="userId != 0 and userId != null">
and userId=#{userId}
</if>
<if test="username != '' and username != null">
and username like "%"#{username}"%"
</if>
<if test="names.size>0 and names != null">
and username in
<foreach item="item" index="index" collection="names" open="("
separator="," close=")">
#{item}
</foreach>
</if>
<choose>
<when test="phonenumber != null">
AND phonenumber like #{phonenumber}
</when>
<when test="address != null">
AND address like #{address}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
order by ${age} asc
</select>
1)加载解析config.xml文件,并解析mapping节点
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
mapperElement(root.evalNode("mappers"));
2)org.apache.ibatis.builder.xml.XMLStatementBuilder的parseStatementNode()方法生成SqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
3)org.apache.ibatis.scripting.xmltags.XMLLanguageDriver的createSqlSource()方法
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
4)org.apache.ibatis.scripting.xmltags.XMLScriptBuilder的parseScriptNode()方法对#和$进行判断和分别执行,调用parsedynamictags()方法解析标签
public SqlSource parseScriptNode() {
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
对于这块之前有讲解过,如果是$符,则直接执行DynamicSqlSource,否则执行RawSqlSource,接着做替换为占位符的调用,这里就不做说明了,主要是讲解复杂标签的问题,所以感兴趣的同学可以自己了解或者看我之前的分析博客
5)parseDynamicTags()方法判断sql的一些标签 解析判断标签 if switch foreach
NodeHandler nodeHandlers(String nodeName) {
Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
map.put("trim", new TrimHandler());
map.put("where", new WhereHandler());
map.put("set", new SetHandler());
map.put("foreach", new ForEachHandler());
map.put("if", new IfHandler());
map.put("choose", new ChooseHandler());
map.put("when", new IfHandler());
map.put("otherwise", new OtherwiseHandler());
map.put("bind", new BindHandler());
return map.get(nodeName);
}
创建对应的IfSqlNode 和ForEachNode对象
private class IfHandler implements NodeHandler {
public IfHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}
private class ForEachHandler implements NodeHandler {
public ForEachHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
String collection = nodeToHandle.getStringAttribute("collection");
String item = nodeToHandle.getStringAttribute("item");
String index = nodeToHandle.getStringAttribute("index");
String open = nodeToHandle.getStringAttribute("open");
String close = nodeToHandle.getStringAttribute("close");
String separator = nodeToHandle.getStringAttribute("separator");
ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
targetContents.add(forEachSqlNode);
}
}
6)MixedSqlNode会调用内部各个sqlNode的apply()方法
//ifSqlNode的apply方法
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
//chooseSqlNode的apply方法
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : ifSqlNodes) {
if (sqlNode.apply(context)) {
return true;
}
}
if (defaultSqlNode != null) {
defaultSqlNode.apply(context);
return true;
}
return false;
}
7)每个apply()方法的实现都会调用StaticTextSqlNode的apply()方法内部实现appendSql()方法
ForEachSqlNode标签验证# 检索替换字符串
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
public void appendSql(String sql) {
GenericTokenParser parser = new GenericTokenParser("#{", "}", new TokenHandler() {
@Override
public String handleToken(String content) {
String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
if (itemIndex != null && newContent.equals(content)) {
newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
}
return new StringBuilder("#{").append(newContent).append("}").toString();
}
});
delegate.appendSql(parser.parse(sql));
}
这样的SQL还只是个半成品,还需要org.apache.ibatis.scripting.xmltags.DynamicSqlSource的getBoundsql()方法
sqlSourceParser.parse()方法,处理方式跟前面处理"${}"占位符的基本一致。
8)使用DynamicSqlSource和RawSqlSource包装成一个完整的可执行BoundSql
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);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
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 = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
//替换为?占位符
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
好了,到这既介绍了复杂标签的解析,又详细的分析了从解析SQL到最后封装成可执行BoundSql