首页 > 数据库 >MyBatis3源码深度解析(二十)动态SQL实现原理(一)动态SQL的核心组件

MyBatis3源码深度解析(二十)动态SQL实现原理(一)动态SQL的核心组件

时间:2024-03-23 18:03:18浏览次数:17  
标签:对象 标签 SqlSource SqlNode 源码 SQL 动态 public

文章目录

前言

在使用JDBC API进行编程时,通常都要对SQL语句进行拼接,甚至还要根据不同的查询条件动态地拼接SQL语句,这个过程是比较繁琐且容易出错的,以致于花费更多的时间。

MyBatis的动态SQL特性就用于解决这个问题。本节先对动态SQL的基本使用,以及涉及到的核心组件进行研究,下一节完整地研究动态SQL的解析过程。

第八章 动态SQL实现原理

8.1 动态SQL的使用

顾名思义,动态SQL指的是事先无法预知具体的条件,需要在运行时根据具体的情况动态地生成SQL语句。

8.1.1 <if>

<if>标签的语法格式是:<if test="OGNL表达式"> SQL代码片段 </if>

如果OGNL表达式的结果为true,则将<if>标签内部的SQL代码片段加入到最终的SQL语句中,否则这一SQL代码片段将被忽略。例如:

<!--UserMapper.xml-->
<select id="selectByCons" parameterType="User" resultType="User">
    select * from user
    where 1 = 1
    <if test="id != null">
        and id = #{id}
    </if>
    <if test="age != null">
        and age > 18
    </if>
</select>

在上面的案例中,根据id和age属性是否为空,动态地构建SQL语句。但不足之处在于,如果id和age属性都为空,则构建的SQL语句为:select * from user where 1 = 1,显然where子句很多余。

8.1.2 <where|trim>

<where>标签的作用就在于弥补<if>标签的不足,它可以构建一个where子句,并解决where子句中因为不同的条件成立时导致的where、and或or关键字多余的问题。例如:

<!--UserMapper.xml-->
<select id="selectByCons" parameterType="User" resultType="User">
    select * from user
    <where>
        <if test="id != null">
            and id = #{id}
        </if>
        <if test="age != null">
            and age > 18
        </if>
    </where>
</select>

在上面的案例中,必须保证至少有一个查询条件时,MyBatis才会在SQL语句中追加where关键字,同时剔除where关键字后相邻的and或or关键字。 如果id和age属性都为空,那么where子句并不会构建。

另外,使用<trim>标签的作用与<where>标签的作用类似,例如上面的案例还可以改成如下所示,两者的效果是一样的:

<!--UserMapper.xml-->
<select id="selectByCons" parameterType="User" resultType="User">
    select * from user
    <trim prefix="where" prefixOverrides="and|or">
        <if test="id != null">
            and id = #{id}
        </if>
        <if test="age != null">
            and age > 18
        </if>
    </trim>
</select>

8.1.3 <choose|when|otherwise>

这几个标签需要组合使用,例如:

<!--UserMapper.xml-->
<select id="selectByConditions" parameterType="User" resultType="User">
    select * from user
    where 1 = 1
    <choose>
        <when test="id != null">
            and id = #{id}
        </when>
        <when test="name != null and name != ''">
            and name like '%${name}%'
        </when>
        <otherwise>
            and age > 18
        </otherwise>
    </choose>
</select>

在上面案例中,所有的<when>标签和<otherwise>标签是互斥的,也就是说只有一个<when>标签成立或最后的<otherwise>标签成立,其余的均不成立。

MyBatis会从上往下依次开始判断<when>标签中的OGNL表达式,一旦有一个<when>标签满足条件,则判定其余的均不成立。当所有的<when>标签都不成立时,则<otherwise>标签成立。

同时,<choose>标签不会追加where关键字,也不会剔除and或or关键字。

8.1.4 <foreach>

<foreach>标签用于对集合参数进行遍历,通常用于构建in条件语句或者insert批量插入语句。例如,当需要以一组ID查询用户信息时:

<!--UserMapper.xml-->
<select id="selectByConditions" parameterType="User" resultType="User">
    select * from user
    where id in
    <foreach item="id" collection="idList" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

在上面案例中,<foreach>标签的collection属性是保存了多个ID值的集合,item属性定义了一个临时变量,从集合中遍历取出的ID值就会赋值到这个变量中。open和close属性分别指要构建的SQL语句片段的开始和结尾字符,separator属性指多个ID值之间的分隔符。

因此,案例中<foreach>标签最终构建出来的SQL语句片段是:(id1,id2,id3,...),整条SQL语句是:select * from user where id in (id1,id2,id3,...)

8.1.5 <set>

<set>标签用于update语句中set子句的构建,可以剔除set子句中多余的逗号。例如:

<!--UserMapper.xml-->
<update id="updateById" parameterType="User">
    update user
    <set>
        <if test="name != null and name != ''">
            name = #{name},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
    </set>
    where id = #{id}
</update>

在上面案例中,如果name属性和age属性均不为空,构建的SQL语句片段是:name = #{name}, age = #{age},,明显会多一个逗号,<set>标签则会将这个多余的逗号去掉。

总结一下,用于构建动态SQL语句的标签主要就是:<if>、<where|trim>、<choose|when|otherwise>、<foreach>、<set>等,用法比较简单,更详细的使用方法可以参考 MyBatis官方文档-动态SQL

8.2 SqlSource组件&BoundSql组件

MyBatis支持两种方式配置SQL信息,一种是通过@Select@Insert@Update@Delete或者@SelectProvider@InsertProvider@UpdateProvider@DeleteProvider等注解,另一种是通过XML配置文件。

SqlSource组件就代表着Java注解或者XML配置文件的SQL资源。 其定义如下:

源码1:org.apache.ibatis.mapping.SqlSource

public interface SqlSource {

  BoundSql getBoundSql(Object parameterObject);
  
}

由 源码1 可知,SqlSource接口只有一个getBoundSql()方法,该方法返回一个BoundSql对象。

借助IDE,可以查看SqlSource接口的四个实现类:

这四个实现类的作用如下:

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

无论是Java注解还是XML配置文件配置的SQL信息,在Mapper调用时都会根据用户传入的参数将Mapper配置转换为StaticSqlSource类。StaticSqlSource类的定义如下:

源码2:org.apache.ibatis.builder.StaticSqlSource

public class StaticSqlSource implements SqlSource {

    // Mapper解析后的SQL语句
    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);
    }

}

由 源码2 可知,StaticSqlSource类封装了Mapper解析后的SQL语句和Mapper参数映射信息,并重写了getBoundSql()方法,该方法根据SQL语句和参数映射信息创建了一个BoundSql对象。

BoundSql是对SQL语句及参数信息的封装,它是SqlSource解析后的结果。 其定义如下:

源码3:org.apache.ibatis.mapping.BoundSql

public class BoundSql {

    // 解析后的SQL语句
    private final String sql;
    // 参数映射信息
    private final List<ParameterMapping> parameterMappings;
    // 参数对象
    private final Object parameterObject;
    // 额外参数信息
    private final Map<String, Object> additionalParameters;
    // 参数对象对应的MetaObject对象
    private final MetaObject metaParameters;
    
    public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings,
    Object parameterObject) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.parameterObject = parameterObject;
        this.additionalParameters = new HashMap<>();
        this.metaParameters = configuration.newMetaObject(additionalParameters);
    }
    
    // ......
}

由 源码3 可知,BoundSql除了封装Mapper解析后的SQL语句和参数映射信息,还封装了Mapper调用时传入的参数对象及其对应的MetaObject对象,以及一些额外的参数信息。这些属性均在BoundSql类的构造方法中初始化。

【MyBatis3源码深度解析(十六)SqlSession的创建与执行(三)Mapper方法的调用过程】中指出,SELECT类型的Mapper方法的调用过程中,会调用BaseExecutor类的query()方法:

源码4:org.apache.ibatis.executor.BaseExecutor

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
        throws SQLException {
    // 根据参数对象获取BoundSql对象
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

由 源码4 可知,BaseExecutor类的query()方法中,会调用MappedStatement对象的getBoundSql()方法获取一个BoundSql对象。

假设有如下Mapper配置:

<!--UserMapper.xml-->
<select id="selectByCons" parameterType="User" resultType="User">
    select * from user where 1 = 1
    <if test="id != null">
        and id = #{id}
    </if>
    <if test="name != null and name != ''">
        and name = #{name}
    </if>
    <if test="age != null">
        and age = #{age}
    </if>
</select>

有如下单元测试:

@Test
public void testBoundSql() throws IOException, NoSuchMethodException {
    Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = new User();
    user.setId(1);
    user.setAge(18);
    // 调用selectByCons()方法
    userMapper.selectByCons(user);
}

借助IDE,可以查看调用selectByCons()方法时的BoundSql对象和SqlSource对象的具体封装的信息。

由图可知,BoundSql对象封装了具体的SQL语句(包含参数占位符)和参数映射信息,以及Mapper调用时传入的参数对象。

另外,MyBatis的任意一个Mapper都有两个内置参数,即_parameter_databaseId_parameter代表参数对象,_databaseId为Mapper配置中通过databaseId属性指定的数据库类型。两者都存放在BoundSql对象的additionalParameters属性中。

由图可知,SqlSource对象中存放的一系列SqlNode对象的实现类,最底层的是StaticSqlSource,这些SqlNode对象的实现类封装了全部SQL节点(SqlNode对象的原理详见8.4节)。

8.3 LanguageDriver组件

MyBatis是通过SqlSource对象描述XML文件或者Java注解中配置的SQL信息的,而SQL配置信息到SqlSource对象的转换是由LanguageDriver组件来完成的。

LanguageDriver组件的定义如下:

源码5:org.apache.ibatis.scripting.LanguageDriver

public interface LanguageDriver {
    // 创建ParameterHandler对象
    ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
    // 创建SqlSource对象
    SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
    SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

由 源码5 可知,LanguageDriver接口有3个方法,其中createParameterHandler()方法用于创建ParameterHandler对象,两个重载的createSqlSource()方法用于创建SqlSource对象。

借助IDE,可以查看LanguageDriver接口的继承结构:

由继承结构可知,LanguageDriver接口有两个实现类:第一个XMLLanguageDriver,它是XML语言驱动,为MyBatis提供了通过XML标签(如<if>、<where>标签)实现动态SQL的功能;第二是RawLanguageDriver,它仅支持静态SQL配置,不支持动态SQL功能。

8.3.1 XMLLanguageDriver

源码6:org.apache.ibatis.scripting.xmltags.XMLLanguageDriver

public class XMLLanguageDriver implements LanguageDriver {

    // 创建ParameterHandler对象,默认实现是DefaultParameterHandler
    @Override
    public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject,
    BoundSql boundSql) {
        return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
    }
    
    // 解析XML文件中的SQL配置信息,创建SqlSource对象
    @Override
    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
        // 创建XMLScriptBuilder对象
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        // 解析SQL资源
        return builder.parseScriptNode();
    }

    // 解析Java注解中的SQL配置信息,创建SqlSource对象
    @Override
    public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
        // 若字符串以<script>标签开头,则以XML方式解析
        if (script.startsWith("<script>")) {
            XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
            return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
        }
        // 解析Java注解中的SQL配置信息
        script = PropertyParser.parse(script, configuration.getVariables());
        TextSqlNode textSqlNode = new TextSqlNode(script);
        // 根据是否是动态SQL语句返回不同的实现类
        if (textSqlNode.isDynamic()) {
            return new DynamicSqlSource(configuration, textSqlNode);
        } else {
            return new RawSqlSource(configuration, script, parameterType);
        }
    }

}

由 源码6 可知,XMLLanguageDriver类实现了LanguageDriver接口中的两个重载的createSqlSource()方法,分别用于处理XML文件和Java注解中配置的SQL信息,他们都能将SQL配置信息转换为SqlSource对象。

第一个重载的createSqlSource()方法用于解析XML文件中的SQL配置信息。 在该方法中,创建了一个XMLScriptBuilder对象,然后调用其parseScriptNode()方法将SQL配置信息转换为SqlSource对象。

源码7:org.apache.ibatis.scripting.xmltags.XMLScriptBuilder

public class XMLScriptBuilder extends BaseBuilder {

    public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
        super(configuration);
        this.context = context;
        this.parameterType = parameterType;
        initNodeHandlerMap();
    }

    public SqlSource parseScriptNode() {
        // 将XNode对象解析转换为MixedSqlNode对象
        MixedSqlNode rootSqlNode = parseDynamicTags(context);
        SqlSource sqlSource;
        if (isDynamic) {
            sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        } else {
            sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        }
        return sqlSource;
    }
    
    // ......
}

由 源码7 可知,创建XMLScriptBuilder对象时,会传入<select|insert|update|delete>标签对应的XNode对象。调用其parseScriptNode()方法时,首先通过parseDynamicTags()方法将XNode对象解析转换为MixedSqlNode对象,再判断是否是动态SQL语句,是的话封装成DynamicSqlSource对象返回,不是的话封装成RawSqlSource对象返回。

第二个重载的createSqlSource()方法用于解析Java注解中的SQL配置信息。 在该方法中,首先判断SQL配置是否以<script>标签开头,如果是,则转调第一个重载的createSqlSource()方法以XML方式处理Java注解中配置的SQL信息,并返回SqlSource对象。

如果不是以<script>标签开头,则通过PropertyParser类的parse()方法替换掉SQL配置中的全局变量。最终再判断是否是动态SQL语句,是的话使用DynamicSqlSource对象描述SQL信息,否则使用RawSqlSource对象描述SQL信息。

8.3.2 RawLanguageDriver

源码8:org.apache.ibatis.scripting.defaults.RawLanguageDriver

public class RawLanguageDriver extends XMLLanguageDriver {

    // 解析XML文件中的SQL配置信息
    @Override
    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
        // 调用父类的createSqlSource()方法创建SqlSource对象
        SqlSource source = super.createSqlSource(configuration, script, parameterType);
        // 校验是否非动态,如果是动态则会抛出异常
        checkIsNotDynamic(source);
        return source;
    }
    
    // 解析Java注解中的SQL配置信息
    @Override
    public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
        // 调用父类的createSqlSource()方法创建SqlSource对象
        SqlSource source = super.createSqlSource(configuration, script, parameterType);
        // 校验是否非动态,如果是动态则会抛出异常
        checkIsNotDynamic(source);
        return source;
    }
    
    private void checkIsNotDynamic(SqlSource source) {
        if (!RawSqlSource.class.equals(source.getClass())) {
            throw new BuilderException("Dynamic content is not allowed when using RAW language");
        }
    }

}

由 源码8 可知,RawLanguageDriver继承自XMLLanguageDriver,重写了XMLLanguageDriver的两个createSqlSource()方法。

在这两个方法中,均直接调用父类XMLLanguageDriver的createSqlSource()方法创建SqlSource对象,唯一不同的是多加了一步:checkIsNotDynamic(source);,这一步会校验SqlSource对象是否非动态,如果是动态的则会抛出异常,因为RawLanguageDriver就是用于静态SQL配置信息的。

8.4 SqlNode组件

8.4.1 SqlNode组件的作用

SqlNode组件用于描述XML配置文件中的SQL节点,例如<if>标签就是一个SQL节点,它对应了一个SqlNode对象。

源码9:org.apache.ibatis.scripting.xmltags.SqlNode

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

由 源码9 可知,SqlNode接口只有一个apply()方法,该方法用于解析SQL节点,根据参数信息生成静态SQL。该方法接收一个DynamicContext对象作为参数,该对象封装了Mapper方法调用时传入的参数信息。

在使用动态SQL时编写的<if>、<where>、<trim>等标签,都对应一种具体的SqlNode实现类。借助IDE,可以列出SqlNode接口的实现类:

这些实现类与XML配置文件中的动态SQL标签对应关系如下:

SqlNode实现类动态SQL标签
IfSqlNode<if>
ChooseSqlNode<choose>
ForEachSqlNode<forcahe>
SetSqlNode<set>
WhereSqlNode<where>
TrimSqlNode<trim>
VarDeclSqlNode<bind>

另外,还有几个比较特殊的实现类:

SqlNode实现类作用
MixedSqlNode用于描述一组SqlNode对象,通常一个Mapper配置是有多个SqlNode对象组成的,这些SqlNode对象通过MixedSqlNode进行关联,组成一个完整的动态SQL配置
StaticTextSqlNode用于描述动态SQL中的静态文本内容
TextSqlNode与StaticTextSqlNode作用类似,不同的地方在于,当静态文本中包含${}占位符时使用,${}需要在Mapper调用时将${}替换为具体的参数值

在这些实现类中,绝大多数都是SqlNode接口的直接子类,但也有例外,即WhereSqlNode和SetSqlNode。如图:

这样设计是因为,<where>标签和<set>标签实际上是<trim>标签的一种特例,<where>标签和<set>标签实现的功能都可以用<trim>标签来完成,因此WhereSqlNode和SetSqlNode是TrimSqlNode的子类,属于特殊的TrimSqlNode。

8.4.2 IfSqlNode的实现原理

源码10:org.apache.ibatis.scripting.xmltags.IfSqlNode

public class IfSqlNode implements SqlNode {
    
    // 用于解析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>标签内的SQL内容对应的SqlNode对象的apply()方法
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
            contents.apply(context);
            return true;
        }
        return false;
    }

}

由 源码10 可知,IfSqlNode类中维护了一个ExpressionEvaluator类的实例,用于根据当前参数对象解析OGNL表达式;还维护了<if>标签的test属性的内容,以及<if>标签中的SQL内容对应的SqlNode对象。

在IfSqlNode的apply()方法中,首先解析test属性指定的OGNL表达式,当表达式的结果为true时,执行<if>标签中的SQL内容对应的SqlNode对象的apply()方法,该方法由具体的SqlNode实现类来完成。

这样就实现了,只有当<if>标签内test属性表达式值为true时,才追加<if>标签内的SQL信息。

8.4.3 StaticTextSqlNode的实现原理

源码11:org.apache.ibatis.scripting.xmltags.StaticTextSqlNode

public class StaticTextSqlNode implements SqlNode {

    // 保存静态SQL文本内容
    private final String text;
    
    public StaticTextSqlNode(String text) {
        this.text = text;
    }
    
    // 追加SQL内容
    @Override
    public boolean apply(DynamicContext context) {
        context.appendSql(text);
        return true;
    }

}

由 源码11 可知,StaticTextSqlNode实现类维护了静态SQL文本内容,调用其apply()方法时,将静态SQL文本内容追加到DynamicContext对象中。

8.4.4 MixedSqlNode的实现原理

前面提到,通常一个Mapper配置中有多个SqlNode对象,这些SqlNode对象通过MixedSqlNode进行关联。

源码12:org.apache.ibatis.scripting.xmltags.MixedSqlNode

public class MixedSqlNode implements SqlNode {
    
    // 保存所有的SqlNode对象的容器
    private final List<SqlNode> contents;
    
    public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
    }
    
    @Override
    public boolean apply(DynamicContext context) {
        // 遍历SqlNode对象,调用所有SqlNode对象的apply()方法
        contents.forEach(node -> node.apply(context));
        return true;
    }
}

由 源码12 可知,MixedSqlNode实现类内部维护了一个List容器,以保存所有的SqlNode对象。调用其apply()方法时,对所有的SqlNode对象进行遍历,以当前DynamicContext对象为参数,调用所有SqlNode对象的apply()方法。

8.4.5 SqlNode组件的使用案例

上面详细研究了三个SqlNode实现类的实现原理,其他的实现类的原理类似。下面通过一个案例来加深一下SqlNode组件的原理。

仍然是上面的Mapper配置:

<!--UserMapper.xml-->
<select id="selectByCons" parameterType="User" resultType="User">
    select * from user where 1 = 1
    <if test="id != null">
        and id = #{id}
    </if>
    <if test="name != null and name != ''">
        and name = #{name}
    </if>
    <if test="age != null">
        and age > 18
    </if>
</select>

从MyBatis动态SQL的角度看,它由4个SqlNode对象构成,分别是3个IfSqlNode和1个StaticTextSqlNode。测试代码如下:

@Test
public void testSqlNode() throws IOException {
    // 1.读取配置文件,创建会话
    Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 2.构建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 and name != ''");
    SqlNode sn4 = new IfSqlNode(new StaticTextSqlNode("and age = #{age}"), "age != null");
    // 3.使用MixedSqlNode将SqlNode组合起来
    MixedSqlNode mixedSqlNode = new MixedSqlNode(Arrays.asList(sn1, sn2, sn3, sn4));
    // 4.构建参数对象
    HashMap<String, Object> paramMap = new HashMap<>();
    paramMap.put("id", "1");
    paramMap.put("age", "18");
    // 5.调用MixedSqlNode的apply()方法
    DynamicContext dynamicContext = new DynamicContext(sqlSession.getConfiguration(), paramMap);
    mixedSqlNode.apply(dynamicContext);
    // 6.获取SQL语句
    System.out.println(dynamicContext.getSql());
}

执行单元测试,控制台打印出SQL语句:

select * from user where 1 = 1 and id = #{id} and age = #{age}

可见,SQL语句根据传入的参数动态地构建出来了。

······

本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析

标签:对象,标签,SqlSource,SqlNode,源码,SQL,动态,public
From: https://blog.csdn.net/weixin_42739799/article/details/136967645

相关文章

  • mysql 索引相关操
    1、创建索引    第一种方式:在执行CREATETABLE时创建索引,例如:     CREATETABLEtest(      idINTauto_incrementPRIMARYKEY,      nameVARCHAR(16),      last_nameVARCHAR(16),       id_......
  • 城管智慧执法系统源码有演示,自主研发,功能完善,正版授权,可商用上项目。
     城管智慧执法系统源码有演示,自主研发,功能完善,正版授权,可商用上项目。   一套数字化的城管综合执法办案系统源码,提供了案件在线办理、当事人信用管理、文书电子送达、沿街店铺视频智能分析等功能,全面赋能执法队员,提高执法队员办案效率。手机端实现考勤、数据采集、上......
  • C语言进阶——动态内存管理
    目录一、C语言底层内存知识补充二、动态内存函数1.1free1.2malloc1.3calloc1.4realloc三、使用常见错误3.1对非动态开辟内存使用free释放3.2空指针未判断造成的错误3.3使用free释放一块动态开辟内存的一部分3.4对同一块动态内存多次释放3.5动态开辟内存没有释放而......
  • 【附源码】java数字家谱管理系统(ssm毕业设计+maven+vue+计算机专业)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义标题:数字家谱管理系统的选题背景及其意义随着信息技术的快速发展,数字化已经成为现代社会的一种趋势。在传统文化的传承与保护方面,数字技术的应用尤为重要。家谱作......
  • 【附源码】java双端的在线学习考试平台(ssm毕业设计+maven+vue+计算机专业)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:在信息技术日益发展的今天,教育行业也在经历着前所未有的变革。传统的面对面教学模式逐渐向线上教育模式转变,这一趋势在全球范围内愈发明显。尤其是在全球......
  • SQL语句:对重复字段的记录,只保留时间最新的一条记录
    selectt.*,t.rowidfromMHQC04_NORMLIZEtwherest_no='HQ4M61WW'orderbytimecreateddesc根据上面表字段和表名称,oracle语句,功能为,对于st_no重复的记录,只保留timecreated时间最新的一条,其余的删除。DELETEFROMMHQC04_NORMLIZEWHERErowidIN(SELECTrow......
  • Golang: Redislock源码分析
    Golang:Redislock源码分析源码https://github.com/bsm/redislock实现Lua脚本obtain.lua--obtain.lua:arguments=>[value,tokenLen,ttl]--Obtain.luatrytosetprovidedkeys'swithvalueandttliftheydonotexists.--Keyscanbeoverrideniftheyal......
  • 【附源码】JAVA计算机毕业设计音乐平台设计与实现(springboot+mysql+开题+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展,互联网已经渗透到人们生活的方方面面,音乐作为人们日常生活的重要娱乐方式,其在线化、平台化的发展趋势日益明显。近年来,音乐平......
  • 【附源码】JAVA计算机毕业设计音乐平台的设计(springboot+mysql+开题+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着数字技术的迅猛发展,音乐产业正在经历一场深刻的变革。传统音乐销售模式逐渐式微,而在线音乐平台以其便捷性、多样性和互动性,迅速占领了市场。当前......
  • sql 处理时间
    mapper中关于时间部分的sql时间范围内查询mapper里使用(这里用的是人大金仓数据库语法,是String的日期进行了转换):<iftest="startTime!=nullandstartTime!=''">AND<![CDATA[b.cjsj>=#{startTime,jdbcType=VARCHAR}]]></if><iftest="endT......