}


如上面的代码所示,SqlSource接口的定义非常简单,只有一个getBoundSql()方法,该方法返回一个BoundSql实例。BoundSql是对SQL语句及参数信息的封装,它是SqlSource解析后的结果。


 我们知道Executor组件与数据库交互,除了需要参数映射信息外,还需要参数信息。因此,Executor组件并不是直接通过StaticSqlSource对象完成数据库操作的,而是与BoundSql交互。BoundSql是对Executor组件执行SQL信息的封装,具体实现代码如下:



/**

  • An actual SQL String got from an {@link SqlSource} after having processed any dynamic content.
  • The SQL may have SQL placeholders “?” and a list (ordered) of a parameter mappings
  • with the additional information for each parameter (at least the property name of the input object to read
  • the value from).
  • Can also have additional parameters that are created by the dynamic language (for loops, bind…).
  • @author Clinton Begin
    */
    public class BoundSql {

private final String sql;
private final List parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;

public BoundSql(Configuration configuration, String sql, List parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}

public String getSql() {
return sql;
}

public List getParameterMappings() {
return parameterMappings;
}

public Object getParameterObject() {
return parameterObject;
}

public boolean hasAdditionalParameter(String name) {
String paramName = new PropertyTokenizer(name).getName();
return additionalParameters.containsKey(paramName);
}

public void setAdditionalParameter(String name, Object value) {
metaParameters.setValue(name, value);
}

public Object getAdditionalParameter(String name) {
return metaParameters.getValue(name);
}
}


如上面的代码所示,BoundSql除了封装了Mapper解析后的SQL语句和参数映射信息外,还封装了Mapper调用时传入的参数对象。另外,MyBatis任意一个Mapper都有两个内置的参数,即\_parameter和\_databaseId。\_parameter代表整个参数,包括<bind>标签绑定的参数信息,这些参数存放在BoundSql对象的additionalParameters属性中。\_databaseId为Mapper配置中通过databaseId属性指定的数据库类型。


SqlSource接口有4个不同的实现,分别为StaticSqlSource、DynamicSqlSource、RawSqlSource和ProviderSqlSource。这4种SqlSource实现类的作用如下。



> 
> * ProviderSqlSource:用于描述通过@Select、@SelectProvider等注解配置的SQL资源信息。
> * DynamicSqlSource:用于描述Mapper XML文件中配置的SQL资源信息,这些SQL通常包含动态SQL配置或者${}参数占位符,需要在Mapper调用时才能确定具体的SQL语句。
> * RawSqlSource:用于描述Mapper XML文件中配置的SQL资源信息,与DynamicSqlSource不同的是,这些SQL语句在解析XML配置的时候就能确定,即不包含动态SQL相关配置。
> * StaticSqlSource:用于描述ProviderSqlSource、DynamicSqlSource及RawSqlSource解析后得到的静态SQL资源。
> 
> 
> 


无论是Java注解还是XML文件配置的SQL信息,在Mapper调用时都会根据用户传入的参数将Mapper配置转换为StaticSqlSource类。我们不妨了解一下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);
}

}


如上面的代码所示,StaticSqlSource类的内容比较简单,只封装了Mapper解析后的SQL内容和Mapper参数映射信息。


### LanguageDriver详解


实际上,SQL配置信息到SqlSource对象的转换是由LanguageDriver组件来完成的。下面来看一下LanguageDriver接口的定义,代码如下:



import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;

public interface LanguageDriver {

/**

  • Creates a {@link ParameterHandler} that passes the actual parameters to the the JDBC statement.
  • @author Frank D. Martinez [mnesarco]
  • @param mappedStatement The mapped statement that is being executed
  • @param parameterObject The input parameter object (can be null)
  • @param boundSql The resulting SQL once the dynamic language has been executed.
  • @return the parameter handler
  • @see DefaultParameterHandler
    */
    ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);

/**

  • Creates an {@link SqlSource} that will hold the statement read from a mapper xml file.
  • It is called during startup, when the mapped statement is read from a class or an xml file.
  • @param configuration The MyBatis configuration
  • @param script XNode parsed from a XML file
  • @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.
  • @return the sql source
    */
    SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);

/**

  • Creates an {@link SqlSource} that will hold the statement read from an annotation.
  • It is called during startup, when the mapped statement is read from a class or an xml file.
  • @param configuration The MyBatis configuration
  • @param script The content of the annotation
  • @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.
  • @return the sql source
    */
    SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);

}


如上面的代码所示,LanguageDriver接口中一共有3个方法,其中createParameterHandler()方法用于创建ParameterHandler对象,另外还有两个重载的createSqlSource()方法,这两个重载的方法用于创建SqlSource对象。MyBatis中为LanguageDriver接口提供了两个实现类,分别为XMLLanguageDriver和RawLanguageDriver。XMLLanguageDriver为XML语言驱动,为MyBatis提供了通过XML标签(我们常用的<if>、<where>等标签)结合OGNL表达式语法实现动态SQL的功能。而RawLanguageDriver表示仅支持静态SQL配置,不支持动态SQL功能。


接下来我们重点了解一下XMLLanguageDriver实现类的内容,代码如下:



public class XMLLanguageDriver implements LanguageDriver {

@Override
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}

// 该方法用于解析XML文件中配置的SQL信息
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
// 创建XMLScriptBuilder对象
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);

// 调用XMLScriptBuilder的parseScriptNode()方法解析SQL资源
return builder.parseScriptNode();

}

// 该方法用于解析XML文件中配置的SQL信息
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// 若字符串以

}

}


如上面的代码所示,XMLLanguageDriver类实现了LanguageDriver接口中两个重载的createSqlSource()方法,分别用于处理XML文件和Java注解中配置的SQL信息,将SQL配置转换为SqlSource对象。



> 
> * 第一个重载的createSqlSource()方法用于处理XML文件中配置的SQL信息,该方法中创建了一个XMLScriptBuilder对象,然后调用XMLScriptBuilder对象的parseScriptNode()方法将SQL资源转换为SqlSource对象。
> * 第二个重载的createSqlSource()方法用于处理Java注解中配置的SQL信息,该方法中首先判断SQL配置是否以<script>标签开头,如果是,则以XML方式处理Java注解中配置的SQL信息,否则简单处理,替换SQL中的全局参数。如果SQL中仍然包含${}参数占位符,则SQL语句仍然需要根据传递的参数动态生成,所以使用DynamicSqlSource对象描述SQL资源,否则说明SQL语句不需要根据参数动态生成,使用RawSqlSource对象描述SQL资源。
> 
> 
> 


从XMLLanguageDriver类的createSqlSource()方法的实现来看,我们除了可以通过XML配置文件结合OGNL表达式配置动态SQL外,还可以通过Java注解的方式配置,只需要注解中的内容加上<script>标签。下面是使用Java注解配置动态SQL的案例代码:


![](https://img-blog.csdnimg.cn/4ce5e8576664488b9b3615808a828b1b.png)


MyBatis从3.2版本开始支持可插拔脚本语言,这允许我们插入一种脚本语言驱动,并基于这种语言来编写动态SQL语句。例如,我们可以让MyBatis的Mapper配置支持Velocity(或者Freemaker)语法,并基于Velocity(或者Freemaker)语法编写动态SQL。


要实现自定义的脚本语言驱动,只需要实现LanguageDriver接口,创建自定义的SqlSource对象,然后对SqlSource对象进行解析,生成最终的BoundSql对象即可。有兴趣的读者可以参考velocity-scripting模块的源码,该模块为MyBatis的Mapper配置提供Velocity语法支持。


接下来笔者就以velocity-scripting模块为例介绍自定义LanguageDriver的使用。要使用velocity-scripting模块,首先需要在项目中添加该模块的依赖,如果是Maven项目,则只需要在pom.xml文件中增加如下内容:


![](https://img-blog.csdnimg.cn/ab9a23bb1750433f9bbf1446659c982c.png)


为了简化LanguageDriver的类型限定名,便于在使用时引用,我们可以在MyBatis主配置文件中为velocity-scripting模块自定义的LanguageDriver指定一个别名,代码如下:


![](https://img-blog.csdnimg.cn/f6850b4f2dab47c48b024010d3f6dbe6.png)


接下来就可以在配置Mapper时使用Velocity语法了,例如:


![](https://img-blog.csdnimg.cn/26a0653cba364fa28e2197eab44fcd7e.png)


要注意的是,在配置Mapper时,需要通过lang属性指定velocity-scripting模块中定义的LanguageDriver的别名。上面代码中的#where()和#in()指令是velocity-scripting模块自定义的指令,更多细节读者可以参考velocity-scripting模块官方文档。


### SqlNode详解


SqlNode用于描述Mapper SQL配置中的SQL节点,它是MyBatis框架实现动态SQL的基石。我们首先来看一下SqlNode接口的内容,代码如下:



public interface SqlNode {
boolean apply(DynamicContext context);
}


如上面的代码所示,SqlNode接口的内容非常简单,只有一个apply()方法,该方法用于解析SQL节点,根据参数信息生成静态SQL内容。apply()方法需要接收一个DynamicContext对象作为参数,DynamicContext对象中封装了Mapper调用时传入的参数信息及MyBatis内置的\_parameter和\_databaseId参数。


在使用动态SQL时,我们可以使用<if>、<where>、<trim>等标签,这些标签都对应一种具体的SqlNode实现类,这些实现类如图9-2所示。


![](https://img-blog.csdnimg.cn/01515b80baca4d9b9f3078445becb699.png)


这些SqlNode实现类的作用如下:



> 
> * IfSqlNode:用于描述动态SQL中<if>标签的内容,XMLLanguageDriver在解析Mapper SQL配置生成SqlSource时,会对动态SQL中的<if>标签进行解析,将<if>标签转换为IfSqlNode对象。
> * ChooseSqlNode:用于描述动态SQL配置中的<choose>标签内容,Mapper解析时会把<choose>标签配置内容转换为ChooseSqlNode对象。
> * ForEachSqlNode:用于描述动态SQL配置中的<foreach>标签,<foreach>标签配置信息在Mapper解析时会转换为ForEachSqlNode对象。
> * MixedSqlNode:用于描述一组SqlNode对象,通常一个Mapper配置是由多个SqlNode对象组成的,这些SqlNode对象通过MixedSqlNode进行关联,组成一个完整的动态SQL配置。
> * SetSqlNode:用于描述动态SQL配置中的<set>标签,Mapper解析时会把<set>标签配置信息转换为SetSqlNode对象。
> * WhereSqlNode:用于描述动态SQL中的<where>标签,动态SQL解析时,会把<where>标签内容转换为WhereSqlNode对象。
> * TrimSqlNode:用于描述动态SQL中的<trim>标签,动态SQL解析时,会把<trim>标签内容转换为TrimSqlNode对象。<where>标签和<set>标签实际上是<trim>标签的一种特例,<where>标签和<set>标签实现的内容都可以使用<trim>标签来完成,因此WhereSqlNode和SetSqlNodel类设计为TrimSqlNode类的子类,属于特殊的TrimSqlNode。
> * StaticTextSqlNode:用于描述动态SQL中的静态文本内容。
> * TextSqlNode:该类与StaticTextSqlNode类不同的是,当静态文本中包含${}占位符时,说明${}需要在Mapper调用时将${}替换为具体的参数值。因此,使用TextSqlNode类来描述。
> * VarDeclSqlNode:用于描述动态SQL中的<bind>标签,动态SQL解析时,会把<bind>标签配置信息转换为VarDeclSqlNode对象。
> 
> 
> 


了解了各个SqlNode实现类的作用后,接下来我们来了解一下SqlNode与动态SQL配置之间的对应关系。假如我们有如下Mapper配置:


![](https://img-blog.csdnimg.cn/cb5e7d5b519c4bc4a82c557eba0bb032.png)


上面是一个完整的Mapper SQL配置,从MyBatis动态SQL的角度来看,它是由4个SqlNode对象构成的。该Mapper配置转换为SqlNode代码如下。


![](https://img-blog.csdnimg.cn/75e385949eba41fbab692fae66a141fe.png)


在上面的代码中,我们创建了一个StaticTextSqlNode和三个IfSqlNode来描述Mapper中动态SQL的配置,其中IfSqlNode由一个StaticTextSqlNode和条件表达式组成。接着创建了一个MixedSqlNode将这些SqlNode组合起来,这样就完成了通过Java对象来描述动态SQL配置。


SqlNode对象创建完毕后,我们就可以调用MixedSqlNode的apply()方法根据参数内容动态地生成SQL内容了。该方法接收一个DynamicContext对象作为参数,DynamicContext对象中封装了Mapper调用时的参数信息。上面的代码中,我们创建了一个DynamicContext,然后调用MixedSqlNode对象的apply()方法,动态SQL的解析结果封装在DynamicContext对象中,我们只需要调用DynamicContext对象的getSql()方法即可获取动态SQL解析后的SQL语句。运行上面这段代码后,生成的SQL内容如下:



select * from user where 1=1 AND id = #{id)


接下来我们再来了解一下SqlNode解析生成SQL语句的过程。首先来看MixedSqlNode的实现,代码如下:



public class MixedSqlNode implements SqlNode {
private final List contents;

public MixedSqlNode(List contents) {
this.contents = contents;
}

@Override
public boolean apply(DynamicContext context) {
contents.forEach(node -> node.apply(context));
return true;
}
}


如上面的代码所示,MixedSqlNode类的实现比较简单,通过一个List对象维护所有的SqlNode对象,MixedSqlNode类的apply()方法中对所有SqlNode对象进行遍历,以当前DynamicContext对象作为参数,调用所有SqlNode对象的apply()方法。接下来我们再来看一下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;
}

}


如上面的代码所示,StaticTextSqlNode实现类比较简单,该类中维护了Mapper配置中的静态SQL节点内容。调用apply()方法时,将静态SQL文本内容追加到DynamicContext对象中。


首先来看TrimSqlNode的实现,代码如下:



public class TrimSqlNode implements SqlNode {

private final SqlNode contents;
private final String prefix; // SQL语句的前缀
private final String suffix; // SQL语句的后缀
private final List prefixesToOverride; // 待重写的前缀
private final List 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 prefixesToOverride, String suffix, List 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);

// 调用委托的SqlNode的apply()方法来解析FilteredDynamicContext
boolean result = contents.apply(filteredDynamicContext);

// 过滤掉脚本中的prefixesToOverride和suffixesToOverride部分
filteredDynamicContext.applyAll();
return result;

}

private static List parseOverrides(String overrides) {
if (overrides != null) {
final StringTokenizer parser = new StringTokenizer(overrides, “|”, false);
final List 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;

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());
}

@Override
public Map<String, Object> getBindings() {
  return delegate.getBindings();
}

@Override
public void bind(String name, Object value) {
  delegate.bind(name, value);
}

@Override
public int getUniqueNumber() {
  return delegate.getUniqueNumber();
}

@Override
public void appendSql(String sql) {
  sqlBuffer.append(sql);
}

@Override
public String getSql() {
  return delegate.getSql();
}

private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
  if (!prefixApplied) {
    prefixApplied = true;
    if (prefixesToOverride != null) {
      // 将sql中开头部分的prefixesToOverride删掉
      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) {
      // 将sql中结尾部分的suffixesToOverride删掉
      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);
    }
  }
}

}

}


然后来看WhereSqlNode的实现,代码如下:



public class WhereSqlNode extends TrimSqlNode {

private static List prefixList = Arrays.asList("AND ","OR ",“AND\n”, “OR\n”, “AND\r”, “OR\r”, “AND\t”, “OR\t”);

public WhereSqlNode(Configuration configuration, SqlNode contents) {
// SQL脚本中的前缀是WHERE,需要
super(configuration, contents, “WHERE”, prefixList, null, null);
}

}


在WhereSqlNode对象的apply()函数中,先调用委托SqlNode对象的apply()函数,然后执行FilteredDynamicContext的函数applyAll()时,会将SQL脚本中开头的AND和OR删掉。



public class SetSqlNode extends TrimSqlNode {

private static final List COMMA = Collections.singletonList(“,”);

public SetSqlNode(Configuration configuration,SqlNode contents) {
super(configuration, contents, “SET”, COMMA, null, COMMA);
}

}


在SetSqlNode对象的apply()函数中,先调用委托SqlNode对象的apply()函数,然后执行FilteredDynamicContext的函数applyAll()时,会将SQL脚本中开头和结尾的","符号删掉。


最后我们了解一下实现动态SQL比较关键的SqlNode实现类之一——IfSqlNode的实现,代码如下:



public class IfSqlNode implements SqlNode {
// evaluator属性用于解析OGNL表达式
private final ExpressionEvaluator evaluator;

// 保存标签test属性内容
private final String test;

// 标签内的SQL内容
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) {
// 如果OGNL表达式值为true,则调用标签内容对应的SqlNode的apply()方法
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}

}


如上面的代码所示,IfSqlNode中维护了一个ExpressionEvaluator类的实例,该实例用于根据当前参数对象解析OGNL表达式。另外,IfSqlNode维护了<if>标签test属性指定的表达式内容和<if>标签中的SQL内容对应的SqlNode对象。在IfSqlNode类的apply()方法中,首先解析test属性指定的OGNL表达式,只有当表达式值为true的情况下,才会执行<if>标签中SQL内容对应的SqlNode的apply()方法。这样就实现了只有当<if>标签test属性表达式值为true的情况下,才会追加<if>标签中配置的SQL信息。


### 动态SQL解析过程


SqlSource用于描述通过XML文件或者Java注解配置的SQL资源信息;SqlNode用于描述动态SQL中<if>、<where>等标签信息;LanguageDriver用于对Mapper SQL配置进行解析,将SQL配置转换为SqlSource对象。要了解MyBatis动态SQL的解析过程,我们可以从XMLLanguageDriver类的createSqlSource()方法出发进行分析,该方法代码如下:



public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}


如上面的代码所示,在XMLLanguageDriver类createSqlSource()方法中,Mapper SQL配置的解析实际上是委托给XMLScriptBuilder类来完成的,该方法中首先创建了一个XMLScriptBuilder对象,然后调用XMLScriptBuilder对象的parseScriptNode()方法完成解析工作。XMLScriptBuilder类的构造函数如下:



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());
}


XMLScriptBuilder类的parseScriptNode()方法代码如下:



public SqlSource parseScriptNode() {
// 调用parseDynamicTags()方法将SQL配置转换为SqlNode对象
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
// 判断Mapper SQL配置中是否包含动态SQL元素,如果是,就创建DynamicSqlSource对象,否则创建RawSqlSource对象
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}


如上面的代码所示,在XMLScriptBuilder类的parseScriptNode()方法中,调用parseDynamicTags()方法将SQL配置转换为SqlNode对象,然后判断SQL配置是否为动态SQL,如果为动态SQL,则创建DynamicSqlSource对象,否则创建RawSqlSource对象。需要注意的是,MyBatis中判断SQL配置是否属于动态SQL的标准是SQL配置是否包含<if>、<where>、<trim>等元素或者${}参数占位符。


接下来,我们再来看一下XMLScriptBuilder类的parseDynamicTags()方法的实现,代码如下:



protected MixedSqlNode parseDynamicTags(XNode node) {
List contents = new ArrayList<>();
NodeList children = node.getNode().getChildNodes();

// 对XML子元素进行遍历
for (int i = 0; i < children.getLength(); i++) {
  XNode child = node.newXNode(children.item(i));

  // 如果子元素为SQL文本内容,则使用TextSqlNode描述该节点
  if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
    String data = child.getStringBody("");
    TextSqlNode textSqlNode = new TextSqlNode(data);

    // 若SQL脚本中包含${}参数占位符,则为动态SQL
    if (textSqlNode.isDynamic()) {
      contents.add(textSqlNode);
      isDynamic = true;
    } else {
      // 如果SQL中不包含${}参数占位符,则不是动态SQL
      contents.add(new StaticTextSqlNode(data));
    }
  } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
    // 如果子元素为<if>、<where>等标签,则使用对应的NodeHandler处理
    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);

}


如上面的代码所示,XMLScriptBuilder类的parseDynamicTags()方法的逻辑相当复杂,在该方法中对SQL配置的所有子元素进行遍历,如果子元素类型为SQL文本,则使用TextSqlNode对象描述SQL节点信息,若SQL节点中存在${}参数占位符,则设置XMLScriptBuilder对象的isDynamic属性值为true;如果子元素为<if>、<where>等标签,则使用对应的NodeHandler处理。


XMLScriptBuilder类中定义了一个私有的NodeHandler接口,并为每种动态SQL标签提供了一个NodeHandler接口的实现类,通过实现类处理对应的动态SQL标签,把动态SQL标签转换为对应的SqlNode对象。


XMLScriptBuilder类中为NodeHandler接口提供了8个实现类,每个实现类用于处理对应的动态SQL标签,例如IfHandler用于处理动态SQL配置中的<if>标签,将<if>标签内容转换为IfSqlNode对象。


![](https://img-blog.csdnimg.cn/572afb4f1c4d41a497abfd52435da6ed.png)


接下来我们来看一下NodeHandler接口的定义,代码如下:



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


如上面的代码所示,NodeHandler接口中只有一个handleNode()方法,该方法接收一个动态SQL标签对应的XNode对象和一个存放SqlNode对象的List对象,handleNode()方法中对XML标签进行解析后,把生成的SqlNode对象添加到List对象中。我们可以参考一下IfHandler类的实现,代码如下:



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

@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
  // 继续调用parseDynamicTags方法解析<if>标签中的子节点
  MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);

  // 获取<if>标签test属性
  String test = nodeToHandle.getStringAttribute("test");
  IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);

  // 将IfSqlNode对象添加到targetContents集合中
  targetContents.add(ifSqlNode);
}

}


在IfHandler类的handleNode()方法中会继续调用XMLScriptBuilder类的parseDynamicTags()方法完成<if>标签子节点的解析,将子节点转换为MixedSqlNode对象,然后获取<if>标签test属性对应的OGNL表达式,接着创建IfSqlNode对象并添加到List对象中。parseDynamicTags()方法的内容前面我们已经分析过了,该方法中会获取当前节点的所有子节点,如果子节点内容为动态SQL标签,继续调用动态SQL标签对应的NodeHandler进行处理,这样就“递归”地完成了所有动态SQL标签的解析。


其他SqlNode实现类的处理逻辑与之类似。例如,下面是ForEachHandler类的实现代码:



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);
}

}


如上面的代码所示,ForEachHandler类的handleNode()方法中也会调用XMLScriptBuilder类的parseDynamicTags()解析<foreach>标签所有子元素,如果子元素中包含<if>标签或<foreach>标签,则继续调用IfHandler或者ForEachHandler对象的handleNode()方法进行处理,直到所有的动态SQL元素全部被转换成SqlNode对象。


需要注意的是,XMLScriptBuilder类的构造方法中,会调用initNodeHandlerMap()方法将所有NodeHandler的实例注册到Map中,代码如下:




#### react和vue的比较

相同
1)vitual dom
2)组件化
3)props,单一数据流

不同点
1)react是jsx和模板;(jsx可以进行更多的js逻辑和操作)
2)状态管理(react)
3)对象属性(vue)
4)vue:view——medol之间双向绑定
5)vue:组件之间的通信(props,callback,emit)

>**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

![](https://img-blog.csdnimg.cn/img_convert/826cf468905203482e43608a3744fe46.webp?x-oss-process=image/format,png)

 = new ForEachSqlNode(configuration, mixedSqlNode, collection, nullable, index, item, open, close, separator);
      targetContents.add(forEachSqlNode);
    }
  }

如上面的代码所示,ForEachHandler类的handleNode()方法中也会调用XMLScriptBuilder类的parseDynamicTags()解析标签所有子元素,如果子元素中包含标签或标签,则继续调用IfHandler或者ForEachHandler对象的handleNode()方法进行处理,直到所有的动态SQL元素全部被转换成SqlNode对象。

需要注意的是,XMLScriptBuilder类的构造方法中,会调用initNodeHandlerMap()方法将所有NodeHandler的实例注册到Map中,代码如下:

react和vue的比较

相同
1)vitual dom
2)组件化
3)props,单一数据流

不同点
1)react是jsx和模板;(jsx可以进行更多的js逻辑和操作)
2)状态管理(react)
3)对象属性(vue)
4)vue:view——medol之间双向绑定
5)vue:组件之间的通信(props,callback,emit)

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

[外链图片转存中…(img-nWOglgl3-1714151440520)]

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐