MyBatis源码-SqlNode语法树

242 阅读2分钟

1. 什么是 SqlNode ?

SqlNode 是Mybatis源码里面的一种业务模型,他用来描述我们写在xml文件里面的动态SQL语句。

sql.png

下面用一个SQL语句举例子

<select id="selectUser" resultType="com.hdu.User">
    select * from users
    <where>
        <if test="id != null">
            and id = #{id}
        </if>
        <if test="name != null">
            and name = #{name}
        </if>
        <if test="age != null">
            and age = #{age}
        </if>
    </where>
</select>

sql2.png

2. 代码示例

StaticTextSqlNode

@Test
public void testStaticTextSqlNode() {
    StaticTextSqlNode staticTextSqlNode = new StaticTextSqlNode("select * from user");
    DynamicContext context = new DynamicContext(configuration, null);
    staticTextSqlNode.apply(
        context
    );
    System.out.println(context.getSql());
    /**
      * select * from user
      */
}

内部实现:

@Override
public boolean apply(DynamicContext context) {
    // 直接执行拼接操作
    context.appendSql(text);
    return true;
}

TextNode

@Test
public void testTrimSqlNode() {
    TextSqlNode textSqlNode = new TextSqlNode("select * from ${table_name}");
    Map<String, String> params = new HashMap<>();
    params.put("table_name", "testTable1");
    DynamicContext context = new DynamicContext(configuration, params);
    textSqlNode.apply(
        context
    );
    System.out.println(context.getSql());
    /**
      * select * from testTable1
      */
}

IfSqlNode

IfSqlNode 就对应 xml 里面的 if 标签

@Test
@Test
public void  testIfSqlNode() {
    User user = new User();
    user.setId("1");
    MixedSqlNode finalSqlNode = new MixedSqlNode(Arrays.asList(
        new StaticTextSqlNode("select * from user"),
        new IfSqlNode(new StaticTextSqlNode("id = #{id}"), "id != null")
    ));
    DynamicContext context = new DynamicContext(configuration, user);
    finalSqlNode.apply(context);
    System.out.println(context.getSql());
    /**
      * select * from user id = #{id}
      */
}

内部实现:

@Override
public boolean apply(DynamicContext context) {
    // 会先判断给定的 test 表达式是否成立, 如果成立 执行 拼接 sql操作
    // id != null
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
        // 执行拼接操作 拼接 id = ? 
        contents.apply(context);
        return true;
    }
    return false;
}

WhereSqlNode

@Test
public void testWhereSqlNode() {

    User user = new User();
    user.setId("1");
    user.setName("张三");

    StaticTextSqlNode staticTextSqlNode = new StaticTextSqlNode("select * from user");

    IfSqlNode idIfSqlNode = new IfSqlNode(new StaticTextSqlNode(" and id = #{id}"), "id != null");
    IfSqlNode nameIfSqlNode = new IfSqlNode(new StaticTextSqlNode(" and name = #{name}"), "name != null");

    MixedSqlNode mixedSqlNode = new MixedSqlNode(Arrays.asList(idIfSqlNode, nameIfSqlNode));
    WhereSqlNode whereSqlNode = new WhereSqlNode(configuration, mixedSqlNode);

    MixedSqlNode finalSqlNode = new MixedSqlNode(Arrays.asList(staticTextSqlNode, whereSqlNode));

    DynamicContext context = new DynamicContext(configuration, user);
    finalSqlNode.apply(
        context
    );
    System.out.println(context.getSql());

    /**
         * select * from user WHERE  id = #{id} and name = #{name}
         */
}

内部实现:

@Override
public boolean apply(DynamicContext context) {
    // 先将SQL暂存
    FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
    // 执行 WhereSqlNode的逻辑,得到的sql先暂存到 filteredDynamicContext
    boolean result = contents.apply(filteredDynamicContext);
    // 对最终的sql进行一些修剪操作,比如删除多余的 AND, 开头添加一个 WHERE
    filteredDynamicContext.applyAll();
    return result;
}

ForEachSqlNode

@Test
public void forEachTest() {
    Map<Object, Object> params = new HashMap<>();
    params.put("list", Arrays.asList(1, 2, 3, 4, 5));
    DynamicContext context = new DynamicContext(configuration, params);
    new StaticTextSqlNode("select * from user where id in").apply(context);
    ForEachSqlNode forEachSqlNode = new ForEachSqlNode(
        configuration,
        new StaticTextSqlNode("#{item}"),
        "list",
        null,
        "item",
        null,
        "(",
        ")",
        ","
    );
    forEachSqlNode.apply(context);
    System.out.println(context.getSql());
    /**
      * select * from user where id in 
      * (  #{__frch_item_0} , 
      *    #{__frch_item_1} , 
      *    #{__frch_item_2} , 
      *    #{__frch_item_3} , 
      *    #{__frch_item_4} 
      * )
      * 同时他会往变量集里面添加参数映射关系
      * #{__frch_item_0} -> 1
      * #{__frch_item_1} -> 2
      * #{__frch_item_2} -> 3
      * #{__frch_item_3} -> 4
      * #{__frch_item_4} -> 5
      */
}

最终示例

@Test
public void testTotal() {
    StaticTextSqlNode staticTextSqlNode = new StaticTextSqlNode("select * from user");
    WhereSqlNode whereSqlNode = new WhereSqlNode
        (
        configuration,
        new MixedSqlNode
        (
            Arrays.asList
            (
                new IfSqlNode(new StaticTextSqlNode(" and id = #{user}"), "id != null"),
                new IfSqlNode(new StaticTextSqlNode(" and name = #{name}"), "name != null"),
                new IfSqlNode(new StaticTextSqlNode(" and age = #{age}"), "age != null")
            )
        )
    );
    MixedSqlNode finalMixedSqlNode = new MixedSqlNode(Arrays.asList(staticTextSqlNode, whereSqlNode));
    User user = new User();
    user.setId("1");
    user.setName("张三");
    user.setAge(18);
    DynamicContext context = new DynamicContext(configuration, user);
    finalMixedSqlNode.apply(context);
    System.out.println(context.getSql());
    /**
      * select * from user 
      * WHERE  id = #{user} 
      * and name = #{name} 
      * and age = #{age}
      */
}