4.3、SqlNode
我们接着来看下一行。
arduino
rootSqlNode.apply(context);
是一个叫SqlNode的类,SqlNode封装了XML中的sql节点,对于多个节点的封装,我们统一使用MixedSqlNode。我们按住ctrl+H可以看到他的继承关系。
SqlNode接口提供了apply方法,是一个函数式接口,接口会传递一个DynamicContext实例,循环遍历每一个标签,并且进行解析,并且在解析的时候都传入了我们的上下文,所以我们在解析一次就会拼接一次sql,最后生成一个可执行的sql,同时会将解析的结果保存在上下文中,特别是sql语句,该方法会针对不同类型的sqlNode进行特殊处理。
我们可以看到很多的老朋友,或许会比较眼熟。
这不就是我们动态sql里面的一些标签吗?常见的SqlNode有如下几个:
- StaticTextSqlNode负责拼接文本
- whereSqlnode处理and关键字,以及两边的空格
- IfSqlNode使用Ognl判断test中的表达式对标签进行过滤
4.3.1、MixedSqlNode
该节点的实现很简单,就是将内部维护的sqlNode集合全部遍历,调用其apply方法,这其实是一个递归的过程,当内部所有的node节点执行完成apply方法后,我们的DynamicContext中也会拼接出一个完整的sql。
java
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
contents.forEach(node -> node.apply(context));
return true;
}
}
4.3.2、StaticTextSqlNode
静态文本节点的apply方法是最简单的实现,直接将文本进行拼接就可以了.
arduino
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;
}
4.3.3、IfSqlNode
对于IfSqlNode我们需要进行判断,其test表达式是否满足条件,如果满足,则处理内部的sqlNode,否则丢弃节点,这也是if标签的作用。
arduino
public class IfSqlNode implements SqlNode {
private final ExpressionEvaluator evaluator;
private final String test;
private final SqlNode contents;
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}
他的解析过程是evaluateBoolean方法。
typescript
//ExpressionEvaluator
public boolean evaluateBoolean(String expression, Object parameterObject) {
// 使用Ognl表达式解析表达式
Object value = OgnlCache.getValue(expression, parameterObject);
if (value instanceof Boolean) {
return (Boolean) value;
}
if (value instanceof Number) {
return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
}
return value != null;
}
// OgnlCache
public static Object getValue(String expression, Object root) {
try {
Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);
return Ognl.getValue(parseExpression(expression), context, root);
} catch (OgnlException e) {
throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
}
}
4.3.4、WhereSqlNode
WhereSqlNode的主要作用是将没用的前缀和后缀消除(掐头去尾),如and、or等。他和setSqlNode都是TrimSqlNode的子类,setSqlNode的作用是消除前后多余的逗号。
scala
public class WhereSqlNode extends TrimSqlNode {
// 处理的前缀集合
private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
public WhereSqlNode(Configuration configuration, SqlNode contents) {
// 使用了super,并不是他自己的方法,使用了父类的方法
super(configuration, contents, "WHERE", prefixList, null, null);
}
}
我们来看看他的父类TrimSqlNode的实现。
ini
public class TrimSqlNode implements SqlNode {
private final SqlNode contents;
private final String prefix;
private final String suffix;
// 需要覆盖的前缀
private final List<String> prefixesToOverride;
private final List<String> suffixesToOverride;
private final Configuration configuration;
public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
}
protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
this.contents = contents;
this.prefix = prefix;
this.prefixesToOverride = prefixesToOverride;
this.suffix = suffix;
this.suffixesToOverride = suffixesToOverride;
this.configuration = configuration;
}
@Override
public boolean apply(DynamicContext context) {
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
// and a =1 and b =1
boolean result = contents.apply(filteredDynamicContext);
// 处理前后缀
filteredDynamicContext.applyAll();
return result;
}
private static List<String> parseOverrides(String overrides) {
if (overrides != null) {
final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
final List<String> list = new ArrayList<>(parser.countTokens());
while (parser.hasMoreTokens()) {
list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
}
return list;
}
return Collections.emptyList();
}
private class FilteredDynamicContext extends DynamicContext {
private DynamicContext delegate;
private boolean prefixApplied;
private boolean suffixApplied;
private StringBuilder sqlBuffer;
// and a =1 and b =1
public FilteredDynamicContext(DynamicContext delegate) {
super(configuration, null);
this.delegate = delegate;
this.prefixApplied = false;
this.suffixApplied = false;
this.sqlBuffer = new StringBuilder();
}
public void applyAll() {
sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
if (trimmedUppercaseSql.length() > 0) {
applyPrefix(sqlBuffer, trimmedUppercaseSql);
applySuffix(sqlBuffer, trimmedUppercaseSql);
}
delegate.appendSql(sqlBuffer.toString());
}
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
if (!prefixApplied) {
prefixApplied = true;
if (prefixesToOverride != null) {
for (String toRemove : prefixesToOverride) {
if (trimmedUppercaseSql.startsWith(toRemove)) {
// 删除前缀
sql.delete(0, toRemove.trim().length());
break;
}
}
}
if (prefix != null) {
sql.insert(0, " ");
sql.insert(0, prefix);
}
}
}
private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
if (!suffixApplied) {
suffixApplied = true;
if (suffixesToOverride != null) {
for (String toRemove : suffixesToOverride) {
if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
int start = sql.length() - toRemove.trim().length();
int end = sql.length();
// 删除后缀
sql.delete(start, end);
break;
}
}
}
if (suffix != null) {
sql.append(" ");
sql.append(suffix);
}
}
}
}
}
4.4、SqlSource
SqlSource为我们提供了一个SQL来源,通过该接口的实现我们可以获得一个SQL。这样的说法可能大家会觉得云里雾里,事实上MyBatis中的SQL是很复杂的,比如我们最长使用的动态SQL,该数据源为我们提供了一种解析SQL变量,如(#{id})以及转化动态sql的能力。MyBatis提供了StaticSqlSource、DynamicSqlSource、RawSqlSource等的实现
我们接下来要debug一下动态标签,所以我们得先伪造一条带有动态标签的sql,我们这里以模糊查询为例。我们先在mapper接口里面写上这个方法。
scss
List<Dept> queryByCondition(Dept dept);
重点是sql的xml文件中我们需要去伪造一下动态标签。
bash
<select id="queryByCondition" resultType="cn.linstudy.pojo.Dept">
select * from dept
<if test="1==1">
where name = #{name}
</if>
</select>
这样的话debug的时候就可以进入到DynamicContext。我们来debug看一下。debug到执行完rootSqlNode.apply(context);
方法后,我们context里面的sqlbuilder。
我们可以看到if标签全部没有了。我们来看一下context.getBindings的结果是什么?
他有两个参数:
- _parameter:表示我们传进来的参数。
- _databaseId:表示数据库标识,我们一般不传。
接着我们来看parse方法,看看里面做了啥骚操作。
ini
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;
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
等到他判断isShrinkWhitespacesInSql这个条件的时候,可以发现为false,进入到sql = parser.parse(originalSql);
这条sql语句。
我们点进去看这个parse方法。
ini
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
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;
do {
// 看看有没有转义符
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);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if ((end <= offset) || (src[end - 1] != '\')) {
expression.append(src, offset, end - offset);
break;
}
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
} while (start > -1);
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
debug后我们可以看看BoundSql里面有啥玩意儿。
其实里面主要就是sql语句和我们传进来的参数映射。第0个参数的名字叫name,具体传进来的参数被封装在parameterObject里面。
4.4.1、StaticSqlSource
该实现类是最简单的实现,可以直接绑定一个静态的sql。
kotlin
public class StaticSqlSource implements SqlSource {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Configuration configuration;
public StaticSqlSource(Configuration configuration, String sql) {
this(configuration, sql, null);
}
public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.configuration = configuration;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
}
4.4.2、DynamicSqlSource
动态sql是我们使用最频繁也是相对来说较为复杂的sqlSource,我们通过解析sqlNode最终还是要将其以节点类型转化为一个sqlSource。
ini
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;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 构建一个上下文
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 这个其中的过程我们已经学习过了,调用apply方法后,context已经保存了解析后的结果
// select id, username, money from account where id = #{id} and username = #{username}
rootSqlNode.apply(context);
// 构建一个sql解析器,重点解析#{},${}等
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
// 获得参数类型
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// 解析动态sql中的变量,如#{id}等
// select id, username, money from account where id = ? and username = ?
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
4.4.3、解析过程
首先我们可以通过测试用例来构建一个sqlSource,xmlLanguageDriver为我们提供了一个创建sqlsource的方法,我们随便写一个xml,在其中编写一个我们最常见的动态sql语句,这个动态sql可以写在任意文件中.
bash
<select id="queryAll" resultMap="AccountMap">
select id, name from dept
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="name != null and name != ''">
and name = #{name}
</if>
</where>
</select>
我们编写一个测试用例来测试一下。
ini
@Test
public void testXMLLanguageDriver() {
XMLLanguageDriver xmlLanguageDriver = new XMLLanguageDriver();
// 1、获取配置类
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis.xml");
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(inputStream);
Configuration configuration = xmlConfigBuilder.parse();
// 2、获取一个sql片段节点
inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("sql.xml");
XPathParser xPathParser = new XPathParser(inputStream,false,null,null);
XNode xNode = xPathParser.evalNode("/select");
// 3、设置查询条件
Dept dept = new Dept();
account.setId(1);
account.setName("XiaoLin");
// 4、创建一个sql源
SqlSource sqlSource = xmlLanguageDriver.createSqlSource(configuration, xNode, Account.class);
System.out.println(sqlSource.getBoundSql(account).getSql());
System.out.println(sqlSource.getBoundSql(account).getParameterObject());
}
本质上,mybatis就是这样实现的,我们可以通过getBoundSql获取一个被绑定的sql,这其中需要一个解析的过程。
ini
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 构建一个上下文
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 这个其中的过程我们已经学习过了,调用apply方法后,context已经保存了解析后的结果
// select id, username, money from account where id = #{id} and username = #{username}
rootSqlNode.apply(context);
// 构建一个sql解析器,重点解析#{},${}等
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
// 获得参数类型
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// 解析动态sql中的变量,如#{id}等
// select id, username, money from account where id = ? and username = ?
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
而sqlSourceParser的解析过程大致长这样。
typescript
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;
// 是否有空白缩进,处理缩进
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
还有一个移除额外的空格的方法。
ini
public static String removeExtraWhitespaces(String original) {
StringTokenizer tokenizer = new StringTokenizer(original);
StringBuilder builder = new StringBuilder();
boolean hasMoreTokens = tokenizer.hasMoreTokens();
while (hasMoreTokens) {
builder.append(tokenizer.nextToken());
hasMoreTokens = tokenizer.hasMoreTokens();
if (hasMoreTokens) {
builder.append(' ');
}
}
return builder.toString();
}
目前我们需要了解一下parser.parse(originalSql)的执行过程,该方法主要完成了以下工作:
- 将#{id}替换为?
- 解析其中的参数
ini
// 解析 #{name}
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token,如果没有 #{ 直接返回原始文本
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;
// 处理#{},${}标签
do {
if (start > 0 && src[start - 1] == '\') {
// 此打开令牌已转义。删除反斜杠并继续。
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// 找到了开启的标签,找关闭标签
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
// select * from user where id =
builder.append(src, offset, start - offset);
// 偏移量就是start+开启标签的长度, #{id}就是#{标签的偏移量+2
offset = start + openToken.length();
// 查询offset之后的closeToken(})的位置作为end点
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\') {
// 这个关闭标记被转义。删除反斜杠并继续。
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
// 生成ognl表达式,#{id}---->id
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// 或拼接一个?,同时设置一个具有映射关系的参数
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
} while (start > -1);
// 将尾部的文本添加进来
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
handleToken的方法如下。
typescript
// content是表达式 ,也就是我们#{}的变量,id和name
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
建立与?的映射关系的参数。
vbnet
private ParameterMapping buildParameterMapping(String content) {
Map<String, String> propertiesMap = parseParameterMapping(content);
// 获取id,就是属性的名字id
String property = propertiesMap.get("property");
Class<?> propertyType;
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
propertyType = metaParameters.getGetterType(property);
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
propertyType = java.sql.ResultSet.class;
} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
propertyType = Object.class;
} else {
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
}
// property-->"id", propertyType-->"java.lang.Integer"
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
// 设置其他属性,如果没有配置则不设置
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if ("javaType".equals(name)) {
javaType = resolveClass(value);
builder.javaType(javaType);
} else if ("jdbcType".equals(name)) {
builder.jdbcType(resolveJdbcType(value));
} else if ("mode".equals(name)) {
builder.mode(resolveParameterMode(value));
} else if ("numericScale".equals(name)) {
builder.numericScale(Integer.valueOf(value));
} else if ("resultMap".equals(name)) {
builder.resultMapId(value);
} else if ("typeHandler".equals(name)) {
typeHandlerAlias = value;
} else if ("jdbcTypeName".equals(name)) {
builder.jdbcTypeName(value);
} else if ("property".equals(name)) {
// Do Nothing
} else if ("expression".equals(name)) {
throw new BuilderException("Expression based parameters are not supported yet");
} else {
throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}. Valid properties are " + PARAMETER_PROPERTIES);
}
}
// 解析使用的typeHandler,最终选择(IntegerTypeHandler)
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();
}
每次解析一个#{}标签都会将其替换成?,同时构建一个参数映射ParameterMapping,这样就保证了可以正常使用preparedStatement进行对数据库的操作了。
4.5、XMLLanguageDriver
java
public class XMLLanguageDriver implements LanguageDriver {
// 第二个参数是script节点
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
// 第二个参数是script文本
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3,判断是否是script标签
// 要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。
// @Update({"<script>select ...<where> .... </script>"})
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);
}
}
}
}
动态sql的解析,这个过程是将sql标签的文本解析成为具有嵌套关系的sql节点实例。
ini
public class XMLScriptBuilder extends BaseBuilder {
private final XNode context;
private boolean isDynamic;
private final Class<?> parameterType;
private final Map<String, NodeHandler> 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;
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());
}
public SqlSource parseScriptNode() {
// 通过解析标签生成一个MixedSqlNode
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
// 构建一个完整的混合标签
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
// 获得所有的子元素
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
// <![CDATA[ ... ]]> 或者 文本标签
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);
}
private interface NodeHandler {
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}
private class TrimHandler implements NodeHandler {
public TrimHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String prefix = nodeToHandle.getStringAttribute("prefix");
String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
String suffix = nodeToHandle.getStringAttribute("suffix");
String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
targetContents.add(trim);
}
}
private class WhereHandler implements NodeHandler {
public WhereHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
// 复杂的标签内会递归调用,知道解析到子元素为纯文本
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
targetContents.add(where);
}
}
private class ForEachHandler implements NodeHandler {
public ForEachHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String collection = nodeToHandle.getStringAttribute("collection");
Boolean nullable = nodeToHandle.getBooleanAttribute("nullable");
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, nullable, index, item, open, close, separator);
targetContents.add(forEachSqlNode);
}
}
private class IfHandler implements NodeHandler {
public IfHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}
// ...省略一些handler实现
}
目前为止,我们已经将一个sql标签解析为mapperstatement的全部过程有了详细的了解,mybatis工程启动时会扫描所有的mapper文件,并将其中的sql标签解析成一个个的mapperstatement,然后进行注册,并保存在了mappedStatements中,其中,mappedStatements其也是Configuration类的一个成员变量。
arduino
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection").conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and " + targetValue.getResource());