深入浅出Mybatis源码解析——SqlSource的创建流程

前言

在前一篇文章深入浅出Mybatis源码解析——映射文件加载流程中,最后说到了创建SqlSource和创建MappedStatement对象,由于篇幅原因最后只好终止了,所以便只好在写一篇文章来说说SqlSource这样的一个创建流程是怎样的,在本系列第一篇完成后,也曾问过一两个认识的读者,有说不太看得懂,可能本人写博客不够图文并茂,所以在下一篇博文中准备来整理一张Mybatis的一个完整的执行流程图,来带读者清楚的了解一下Mybatis的这样一个执行流程。

本篇文章即将开始,请系好安全带!

一、动态SQL标签处理器

再正式开始之前,先来看下createSqlSource方法的代码具体实现,代码如下:

@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) {
	// 初始化了动态SQL标签处理器
	XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
	// 解析动态SQL
	return builder.parseScriptNode();
}

注意这个方法是在XMLLanguageDriver.java这个类中,既然我们看到了入口的地方,那就看看Mybatis的SqlSource是如创建的,上面的代码中首先构建了XMLScriptBuilder这个对象,我觉得有必要看看这个构造方法做了什么,因为我感觉这个构造函数不同凡响。废话不多说,直接上代码:

	public XMLScriptBuilder(Configuration configuration, XNode context, Class parameterType) {
		super(configuration);
		this.context = context;
		this.parameterType = parameterType;
		// 初始化动态SQL中的节点处理器集合
		initNodeHandlerMap();
	}

	private void initNodeHandlerMap() {
		nodeHandlerMap.put("trim", new TrimHandler());
		nodeHandlerMap.put("where", new WhereHandler());
		nodeHandlerMap.put("set", new SetHandler());
		nodeHandlerMap.put("foreach", new ForEachHandler());
		nodeHandlerMap.put("if", new IfHandler());
		nodeHandlerMap.put("choose", new ChooseHandler());
		nodeHandlerMap.put("when", new IfHandler());
		nodeHandlerMap.put("otherwise", new OtherwiseHandler());
		nodeHandlerMap.put("bind", new BindHandler());
	}

这就是不同凡响?可能有人是怀疑的,但是你看看initNodeHandlerMap方法中存了这么多对象,这些对象基本上都是mapper文件中的一些where、foreach、if等这些标签对象。因此我想着nodeHandlerMap肯定不是个普通的map集合。那我们来简单的看下nodeHandlerMap:

private final Map nodeHandlerMap = new HashMap<>();

private interface NodeHandler {
	void handleNode(XNode nodeToHandle, List targetContents);
}

private class BindHandler implements NodeHandler {
	public BindHandler() {
		// Prevent Synthetic Access
	}

	@Override
	public void handleNode(XNode nodeToHandle, List targetContents) {
		final String name = nodeToHandle.getStringAttribute("name");
		final String expression = nodeToHandle.getStringAttribute("value");
		final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
		targetContents.add(node);
	}
}

private class WhereHandler implements NodeHandler {
	public WhereHandler() {
		// Prevent Synthetic Access
	}

	@Override
	public void handleNode(XNode nodeToHandle, List targetContents) {
                // 混合SQL节点
		MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
		WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
		targetContents.add(where);
	}
}

从上面的代码中可以看出,nodeHandlerMap的存储的是NodeHandler这么个内部接口对象,那么在initNodeHandlerMap方法中所存的对象肯定是去实现NodeHandler的。因此在这里列出了两个相关的实现类代码,对于WhereHandler实现的代码里的parseDynamicTags方法,接下来我们会进行说明,因为那些实现的对象中,大多数调用了这个方法。我们还是继续回头看下builder.parseScriptNode()。

二、解析动态SQL

builder.parseScriptNode()的调用还是在createSqlSource方法中,既然如此,那便看看这个parseScriptNode方法到底做了什么:

public SqlSource parseScriptNode() {
	// 获取解析过的SQL信息(解析了动态SQL标签和${})
	// 注意:此时SQL语句中还有#{}没有处理
	MixedSqlNode rootSqlNode = parseDynamicTags(context);
	SqlSource sqlSource = null;
	// 如果包含${}和动态SQL语句,就是dynamic的
	if (isDynamic) {
		sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
	} else {
		// 否则是RawSqlSource的(带有#{}的SQL语句)
		sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
	}
	return sqlSource;
}

方法很简单,就是首先通过parseDynamicTags方法来获得一个MixedSqlNode对象,接着开始判断这个SQL是否是动态的,判断的标准是如果包含${}和动态SQL语句,那么就是dynamic的SQL,否则就是RawSqlSource。可能有同学在想这个isDynamic是怎么来的,不要着急等下就会揭晓这个问题。

我们先来看下parseDynamicTags到底做了什么,然后分别看下DynamicSqlSource和RawSqlSource对象的构造方法。

protected MixedSqlNode parseDynamicTags(XNode node) {
	List contents = new ArrayList<>();
	//获取