SqlNode用于描述Mapper SQL配置中的SQL节点,它是MyBatis框架实现动态SQL的基石。我们首先来看一下SqlNode接口的内容,代码如下: 如上面的代码所示,SqlNode接口的内容非常简单,只有一个apply()方法,该方法用于解析SQL节点,根据参数信息生成静态SQL内容。apply()方法需要接收一个DynamicContext对象作为参数,DynamicContext对象中封装了Mapper调用时传入的参数信息及MyBatis内置的_parameter和_databaseId参数。 在使用动态SQL时,我们可以使用<if>、<where>、<trim>等标签,这些标签都对应一种具体的SqlNode实现类,这些实现类如图所示。 这些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对象。在学习MyBatis动态SQL使用时,我们了解到<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配置: 上面是一个完整的Mapper SQL配置,从MyBatis动态SQL的角度来看,它是由4个SqlNode对象构成的。该Mapper配置转换为SqlNode代码如下. 在上面的代码中,我们创建了一个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的实现,代码如下: 如上面的代码所示,MixedSqlNode类的实现比较简单,通过一个List对象维护所有的SqlNode对象,MixedSqlNode类的apply()方法中对所有SqlNode对象进行遍历,以当前DynamicContext对象作为参数,调用所有SqlNode对象的apply()方法。接下来我们再来看一下StaticTextSqlNode的实现,代码如下: 如上面的代码所示,StaticTextSqlNode实现类比较简单,该类中维护了Mapper配置中的静态SQL节点内容。调用apply()方法时,将静态SQL文本内容追加到DynamicContext对象中。 最后我们了解一下实现动态SQL比较关键的SqlNode实现类之一——IfSqlNode的实现,代码如下: 如上面的代码所示,IfSqlNode中维护了一个ExpressionEvaluator类的实例,该实例用于根据当前参数对象解析OGNL表达式。另外,IfSqlNode维护了<if>标签test属性指定的表达式内容和<if>标签中的SQL内容对应的SqlNode对象。 在IfSqlNode类的apply()方法中,首先解析test属性指定的OGNL表达式,只有当表达式值为true的情况下,才会执行<if>标签中SQL内容对应的SqlNode的apply()方法。这样就实现了只有当<if>标签test属性表达式值为true的情况下,才会追加<if>标签中配置的SQL信息。 其他SqlNode实现类(例如ForEachSqlNode、TrimSqlNode)的原理与IfSqlNode类似,可自行阅读其源码。public interface SqlNode {
boolean apply(DynamicContext context);
}
<select id="getUserByEntity" resultType="com.blog4java.mybatis.example.entity.UserEntity">
select
<include refid="userAllField"/>
from user
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="name != null">
AND name = #{name}
</if>
<if test="phone != null">
AND phone = #{phone}
</if>
</where>
</select>
@Test
public void testSqlNode() {
// 构建SqlNode
SqlNode sn1 = new StaticTextSqlNode("select * from user where 1=1");
SqlNode sn2 = new IfSqlNode(new StaticTextSqlNode(" AND id = #{id}"),"id != null");
SqlNode sn3 = new IfSqlNode(new StaticTextSqlNode(" AND name = #{name}"),"name != null");
SqlNode sn4 = new IfSqlNode(new StaticTextSqlNode(" AND phone = #{phone}"),"phone != null");
SqlNode mixedSqlNode = new MixedSqlNode(Arrays.asList(sn1, sn2, sn3, sn4));
// 创建参数对象
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("id","1");
// 创建动态SQL解析上下文
DynamicContext context = new DynamicContext(sqlSession.getConfiguration(),paramMap);
// 调用SqlNode的apply()方法解析动态SQL
mixedSqlNode.apply(context);
// 调用DynamicContext对象的getSql()方法获取动态SQL解析后的SQL语句
System.out.println(context.getSql());
}
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : contents) {
sqlNode.apply(context);
}
return true;
}
}
public class StaticTextSqlNode implements SqlNode {
// 静态SQL文本内容
private final String text;
public StaticTextSqlNode(String text) {
this.text = text;
}
@Override
public boolean apply(DynamicContext context) {
// 追加SQL内容
context.appendSql(text);
return true;
}
}
public class IfSqlNode implements SqlNode {
// evaluator属性用于解析OGNL表达式
private final ExpressionEvaluator evaluator;
// 保存<if>标签test属性内容
private final String test;
// <if>标签内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,则调用<if>标签内容对应的SqlNode的apply()方法
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}