MyBatis源码分析之Script用法详解
Posted 叶长风
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis源码分析之Script用法详解相关的知识,希望对你有一定的参考价值。
MyBatis源码分析之Script用法详解
在上一篇文章中讲到MyBatis的#paras和$paras用法,在里面提到在解析sql组装成SqlSource对象时,会判断当前sql是否是动态类型,然后里面有一个对sql中是否含有script的判断,这种用法我以前也没有用过,就看到同事写过一回,感觉和xml中的写法差不多,就是一种动态sql,可以有一些if、else之类的条件判断,今天就来了解一下这个用法,顺便看下源码实现。
1. Script用法
与之前相同,讲到这里还是先讲一下
@ResultMap("BaseResultMap")
@Select("<script>" +
"select * from user " +
"<where>" +
"<if test=\\"age != null\\"> age = #age</if>" +
"</where>" +
"</script>")
List<User> getUser4(@Param("age") Integer age);
判断传进去的age字段是否为空,为空则查询全部,不为空则查询对应字段值下的记录,这个动态sql其实写的挺麻烦的,还要转义,注意script的结束等等,我觉得还不如xml写起来方便。
调用程序为:
List<User> users = userMapper.getUser4(null);
System.out.println(users);
System.out.println();
List<User> users2 = userMapper.getUser4(25);
System.out.println(users2);
查询的结果和预料中差不多,users中为全部记录,users2中为1条记录。
2. 源码解析
上述中讲述了
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType)
// issue #3
if (script.startsWith("<script>"))
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
else
// issue #127
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic())
return new DynamicSqlSource(configuration, textSqlNode);
else
return new RawSqlSource(configuration, script, parameterType);
可以转到createSqlSource方法看一看。
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType)
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
public SqlSource parseScriptNode()
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
if (isDynamic)
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
else
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
return sqlSource;
在parseScriptNode中进行了具体的sql类型判断,其中如果isDynamic类型为true,则返回DynamicSqlSource,否则返回RawSqlSource,isDynamic在parseDynamicTags方法中进行初始化,进这个方法看看。
protected MixedSqlNode parseDynamicTags(XNode node)
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++)
XNode child = node.newXNode(children.item(i));
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);
这个方法用来解析节点,调试一下看下。
此时node节点内容与我们之前查看的sql相同,中间分析的过程非常多,比较复杂,这就不再单独看了,可以直接看这个方法的返回。
这里返回的rootSQLNode中contents对象有两个,一个为固定查询sql,一个为条件sql,最后在这返回DynamicSqlSource对象。
最后我看执行到executor中的query时的代码。
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
调试到getBoundSql处,拿到BoundSql查看,两次分别的结果为:
在传入age为null时,返回为不带查询条件的sql。
在age不为null时,此时返回的sql为:
此时就有age查询条件。我们进getBoundSql方法中查看具体细节。
public BoundSql getBoundSql(Object parameterObject)
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty())
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings())
String rmId = pm.getResultMapId();
if (rmId != null)
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null)
hasNestedResultMaps |= rm.hasNestedResultMaps();
在调试代码时可知此时获得的boundSql对象已经是组装完成的sql。
此时已经将sql组装完成了,我们继续进SqlSource方法中看看。
@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);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet())
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
return boundSql;
在调用rootSqlNode.apply方法,在前面分析知这里的SqlNode是MixedSqlNode.
@Override
public boolean apply(DynamicContext context)
for (SqlNode sqlNode : contents)
sqlNode.apply(context);
return true;
此时的contents对象就是两个,一个为固定sql,一个是where条件,如下图:
第一个sqlNode没啥说的,固定sql调用append方就可以了,如下:
@Override
public boolean apply(DynamicContext context)
context.appendSql(text);
return true;
进入where条件的SQLNode,where条件没有实现apply方法,此处调用的为父类TrimSqlNode类的apply方法,apply方法为:
@Override
public boolean apply(DynamicContext context)
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
boolean result = contents.apply(filteredDynamicContext);
filteredDynamicContext.applyAll();
return result;
在调用contents.apply(filteredDynamicContext)方法时还要再回到MixSqlNode处,此时调用就是IfSqlNode。
此时的动态sql就是where中的条件了,然后后续filteredDynamicContext.applyAll()中的操作就是给sql加上where前缀,这里就不再分析了,有兴趣的可以自己看看。
Script用法比较简单,我原想原理应该也是和之前差不多,没想到分析进来这么多处理和判断,可以说看得晕头转向,此处先留个记号,以后有空还要再回来仔细分析一下。
以上是关于MyBatis源码分析之Script用法详解的主要内容,如果未能解决你的问题,请参考以下文章
深入浅出Mybatis系列---配置详解之typeAliases别名(mybatis源码篇)