在上几章内容中,我们讲述了Mapper资源文件的解析,在最后关头的MapperStatement构建中,我们有一个重要的地方没有涉及到,今天我们就来深入这一块内容。那这是什么呢?其实就是sql动态语句的构建。在MyBatis中,这块功能也是它的亮点之一。
在Mapper解析过程中,我们会有这样一个调用方法:
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
1. langDriver从何而来
我们先来看它的初始化方法:
LanguageDriver langDriver = getLanguageDriver(lang);
private LanguageDriver getLanguageDriver(String lang) {
Class langClass = null;
if (lang != null) {
langClass = resolveClass(lang);
}
return configuration.getLanguageDriver(langClass);
}
public LanguageDriver getLanguageDriver(Class langClass) {
if (langClass == null) {
return languageRegistry.getDefaultDriver();
}
languageRegistry.register(langClass);
return languageRegistry.getDriver(langClass);
}
这里我们一般都不会设置特定的LanguageDriver,那么默认的LanguageDriver是哪里设置进来的呢?
是在Configuration的构造函数中:
public Configuration() {
。。。
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
所以我们知道了如果没有特殊指定,那我们将会使用XMLLanguageDriver来构建SqlSource。
2. SqlSource的构建
我们进入createSqlSource方法:
//XMLLanguageDriver
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
我们有看到了一个Builder相关类,我们继续来看这个源码。
2.1 XMLScriptBuilder 源码分析
我们先来看它的属性和构造方法:
public class XMLScriptBuilder extends BaseBuilder {
private final XNode context;
private boolean isDynamic;
private final Class parameterType;
private final Map nodeHandlerMap = new HashMap<>();
public XMLScriptBuilder(Configuration configuration, XNode context) {
this(configuration, context, null);
}
public XMLScriptBuilder(Configuration configuration, XNode context, Class parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
//<1> 初始化nodeHandler
initNodeHandlerMap();
}
。。。
}
这里关键的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());
}
看到这里就有点欣喜了,因为这就是我们常用的动态Sql的节点。我们继续来看它的方法:
public SqlSource parseScriptNode() {
//<1> 解析sqlNode节点
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
这里中的<1>方法是最重要的:
protected MixedSqlNode parseDynamicTags(XNode node) {
List contents = new ArrayList<>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
// <1>如果不包含其他标签或者是纯文本节点
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
这里这样强看的话,会让人很不舒服,也不能理解,所以我们结合例子来看。
假设我们的资源文件内容是(这是源码中的Test,路径为org/apache/ibatis/builder/AuthorMapper.xml):
OK,根据这个例子,我们可以看到在parseDynamicTags进入了<1>中的逻辑,也就是:
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
而在判断isDynamic方法,我们进入查看:
public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
}
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
因为我们是纯文本的sql,所以我们直接会进入添加StaticTextSqlNode方法:
contents.add(new StaticTextSqlNode(data));
//StaticTextSqlNode
public class StaticTextSqlNode implements SqlNode {
private final String text;
public StaticTextSqlNode(String text) {
this.text = text;
}
@Override
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
}
//之后直接返回了
return new MixedSqlNode(contents);
如果接的理不清楚的话,建议调试代码进行理解。
那么退出来之后我们又是进入了:
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
我们知道isDynamic是false,那么我们继续来看RawSqlSource这块的逻辑:
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();
}
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<>());
}
我们继续会进入到sqlSourceParser.parse方法:
public class SqlSourceBuilder extends BaseBuilder {
private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
public SqlSourceBuilder(Configuration configuration) {
super(configuration);
}
public SqlSource parse(String originalSql, Class parameterType, Map 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());
}
其中这里的StaticSqlSource为
public class StaticSqlSource implements SqlSource {
private final String sql;
private final List parameterMappings;
private final Configuration configuration;
public StaticSqlSource(Configuration configuration, String sql) {
this(configuration, sql, null);
}
public StaticSqlSource(Configuration configuration, String sql, List parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.configuration = configuration;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
}
3. 今日总结
今天我们大致分析了createSqlSource的大致流程,其中有很多我们肯定还是没有搞懂,我大致整理下了我们欠下的技术债
sqlNode
nodeHandler
sqlSource
ParameterMapping
之后我们会慢慢分析。