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

文章目录

  • 前言
  • 第八章 动态SQL实现原理
    • 8.1 动态SQL的使用
      • 8.1.1 \<if>
      • 8.1.2 <where|trim>
      • 8.1.3 <choose|when|otherwise>
      • 8.1.4 \<foreach>
      • 8.1.5 \<set>
    • 8.2 SqlSource组件&BoundSql组件
    • 8.3 LanguageDriver组件
      • 8.3.1 XMLLanguageDriver
      • 8.3.2 RawLanguageDriver
    • 8.4 SqlNode组件
      • 8.4.1 SqlNode组件的作用
      • 8.4.2 IfSqlNode的实现原理
      • 8.4.3 StaticTextSqlNode的实现原理
      • 8.4.4 MixedSqlNode的实现原理
      • 8.4.5 SqlNode组件的使用案例

前言

在使用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资源。 其定义如下:

源码1org.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类的定义如下:

源码2org.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解析后的结果。 其定义如下:

源码3org.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()方法:

源码4org.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组件的定义如下:

源码5org.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

源码6org.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对象。

源码7org.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

源码8org.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对象。

源码9org.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的实现原理

源码10org.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的实现原理

源码11org.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进行关联。

源码12org.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源码深度解析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/481553.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode 20.有效的括号 JAVA

题目 思路 括号的匹配&#xff0c;这是一道经典的栈的应用问题。 给我们一个字符串&#xff0c;当我们遍历到左括号时&#xff0c;让其入栈。当我们遍历到右括号时&#xff0c;让栈顶元素出栈&#xff0c;看看栈顶的元素是否和遍历到的右括号匹配。不匹配的话直接false,匹配的…

vue2 脚手架

安装 文档&#xff1a;https://cli.vuejs.org/zh/ 第一步&#xff1a;全局安装&#xff08;仅第一次执行&#xff09; npm install -g vue/cli 或 yarn global add vue/cli 备注&#xff1a;如果出现下载缓慢&#xff1a;请配置npm 淘宝镜像&#xff1a; npm config set regis…

java算法第32天 | ● 122.买卖股票的最佳时机II ● 55. 跳跃游戏 ● 45.跳跃游戏II

122.买卖股票的最佳时机II 本题中理解利润拆分是关键点&#xff01; 不要整块的去看&#xff0c;而是把整体利润拆为每天的利润。假如第 0 天买入&#xff0c;第 3 天卖出&#xff0c;那么利润为&#xff1a;prices[3] - prices[0]。 相当于(prices[3] - prices[2]) (prices[…

XSS-labs详解

xss-labs下载地址https://github.com/do0dl3/xss-labs 进入靶场点击图片&#xff0c;开始我们的XSS之旅&#xff01; Less-1 查看源码 代码从 URL 的 GET 参数中取得 "name" 的值&#xff0c;然后输出一个居中的标题&#xff0c;内容是 "欢迎用户" 后面…

手撕算法-买卖股票的最佳时机 II(买卖多次)

描述 分析 使用动态规划。dp[i][0] 代表 第i天没有股票的最大利润dp[i][1] 代表 第i天持有股票的最大利润 状态转移方程为&#xff1a;dp[i][0] max(dp[i-1][0], dp[i-1][1] prices[i]); // 前一天没有股票&#xff0c;和前一天有股票今天卖掉的最大值dp[i][1] max(dp[i-1…

智能财务新选择!Zoho Books入选福布斯榜单,助力中小企业!

放眼全球&#xff0c;中小企业始终是经济发展的重要组成部分。然而&#xff0c;由于中小企业的规模、流程规范和资源等方面受限较多&#xff0c;从而导致其在管理及运营上存在着诸多问题。其中包括财务管理不规范、成本控制不到位、运营效率低下等&#xff0c;这些问题则直接影…

如何在CentOS安装SQL Server数据库并实现无公网IP远程连接内网数据库

文章目录 前言1. 安装sql server2. 局域网测试连接3. 安装cpolar内网穿透4. 将sqlserver映射到公网5. 公网远程连接6.固定连接公网地址7.使用固定公网地址连接 前言 简单几步实现在Linux centos环境下安装部署sql server数据库&#xff0c;并结合cpolar内网穿透工具&#xff0…

基于GD32E230C8T6的数字示波器

基于GD32E230C8T6的数字示波器 文章目录 基于GD32E230C8T6的数字示波器基于GD32E230C8T6的数字示波器实物演示电路原理**模拟前端处理电路**image.png**交直流耦合电路****输入信号衰减电路****信号调理电路****虚断:****虚短:****电压跟随器****反相比例放大器****同相比例放…

深度剖析GNSS高精度定位原理

一、背景 目前室外使用最广泛的定位手段是GNSS定位&#xff0c;常规的GNSS定位精度约5、10米左右&#xff0c;无法满足高精度场景的应用&#xff0c;如何提升GNSS定位性能是亟待解决的问题。本文由浅入深剖析GNSS定位原理并介绍如何实现厘米级高度定位。 二、GNSS定位原理 1、…

MySQL下载安装和本地连接

1、下载MySQL 从MySQL官网下载MySQL Community Server版本&#xff1a; 下载地址&#xff1a;MySQL官网 1、进入官网&#xff0c;点击DOWNLOADS 2、点击MySQL Community(GPL)Downloads 3、点击MySQL Installer for Windows 4、这个会直接跳转到最新的版本 如果想下载以往的…

面试算法-83-不同路径 II

题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish”&#xff09;。 现在考虑网格中有障碍物。那么从左上角到…

【进程概念】启动进程 | 查看进程 | 创建进程

目录 启动进程 查看进程 方法1&#xff1a;/proc 方法2&#xff1a;查看脚本 ​方法3&#xff1a;系统调用获取进程标示符❗❗ 终止进程 创建进程&#xff08;主fork) &#x1f642;查看父子进程的pid &#x1f642;进程创建/执行/终止 &#x1f642;多次重新启动进…

java的IO之NIO

NIO是一种同步非阻塞的I/O模型&#xff0c;在Java 1.4中引入了NIO框架&#xff0c;对应java.nio包&#xff0c;提供了channel、selector、buffer等。 NIO中的N可以理解为Non-blocking不在单纯是New&#xff0c;它支持面向缓冲的&#xff0c;基于通道的I/O操作方法。NIO提供了与…

论文阅读之LORA: LOW-RANK ADAPTATION OF LARGE LAN- GUAGE MODELS(2021)

文章目录 论文地址主要内容主要贡献模型图技术细节实验结果 论文地址 LORA: LOW-RANK ADAPTATION OF LARGE LAN- GUAGE MODELS 主要内容 这篇文章的主要内容是介绍了一种名为LoRA&#xff08;Low-Rank Adaptation&#xff09;的技术&#xff0c;这是一种针对大型语言模型进行…

阅读MySQL知识4

一、MySQL数据库主从同步延迟产生的原因 MySQL的主从复制都是单线程的操作&#xff0c;主库对所有DDL和DML产生的日志写进binlog&#xff0c;由于binlog是顺序写&#xff0c;所以效率很高。 Slave的SQL Thread线程将主库的DDL和DML操作事件在slave中重放。DML和DDL的IO操作…

【欧拉函数+快速幂】第十四届蓝桥杯省赛C++ C组 Java A组/研究生组 Python 研究生组《互质数的个数》(C++)

【题目描述】 给定 a,b&#xff0c;求 1≤x< 中有多少个 x 与 互质。 由于答案可能很大&#xff0c;你只需要输出答案对 998244353 取模的结果。 【输入格式】 输入一行包含两个整数分别表示 a,b&#xff0c;用一个空格分隔。 【输出格式】 输出一行包含一个整数表示…

8-深度学习

声明 本文章基于哔哩哔哩付费课程《小白也能听懂的人工智能原理》。仅供学习记录、分享&#xff0c;严禁他用&#xff01;&#xff01;如有侵权&#xff0c;请联系删除 目录 一、知识引入 &#xff08;一&#xff09;深度学习 &#xff08;二&#xff09;Tensorflo…

Java全栈课程之Linux———基本属性

一、看懂文件属性 Linux系统是一种典型的多用户系统&#xff0c;不同的用户处于不同的地位&#xff0c;拥有不同的权限。为了保护系统的安全性&#xff0c;Linux系统对不同的用户访问同一文件&#xff08;包括目录文件&#xff09;的权限做了不同的规定。 在Linux中我们可以使…

深入理解Ubuntu22:探索Linux操作系统的功能与应用

一、linux &#xff08;一&#xff09;、安装 1、电脑可以安装双系统&#xff0c;即在一套硬件上只能同时运行一个操作系统&#xff0c;例&#xff1a;C盘安装win&#xff0c;D盘安装linux。 2、虚拟机 虚拟机需要硬件支持&#xff0c;并需开启VT-x. 如&#xff1a;Virtual…

Ubuntu18.04显示--有线连接未托管

引用: Ubuntu18.04连不网 报"有线连接未托管"_ubuntu20.04以太网未托管-CSDN博客 正文 虚拟机环境配置&#xff1a; VirtaualBox Ubuntu18.04桌面版 问题现象&#xff1a; Ubuntu18.04虚拟机的桌面上提示“有线连接未托管”&#xff0c;虚拟机不能上网&#xf…