抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

MyBatis动态SQL

动态SQL是MyBatis重要特性之一。

动态SQL类型

if

最常见的场景就是根据条件包含where子句的一部分。

<select id="findActiveBlogWithTitleLike" resultType="Blog">
    SELECT * FROM BLOG
    WHERE state = ‘ACTIVE’
    <if test="title != null">
    AND title = #{title}
    </if>
</select>

choose,when,otherwise

不想使用所有条件,而是中多个条件中选择一个。

<select id="findActiveBlogLike" resultType="Blog">
    SELECT * FROM BLOG WHERE state = ‘ACTIVE’
    <choose>
        <when test="title != null">
            AND title like #{title}
        </when>
        <when test="author != null and author.name != null">
            AND author_name like #{author.name}
        </when>
        <otherwise>
            AND featured = 1
        </otherwise>
    </choose>
</select>

trim,where,set

如果不匹配

SELECT * FROM BLOG
WHERE

如果只匹配第二个条件

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

都会导致查询失败。可以使用以下方式避免。

<select id="findActiveBlogLike" resultType="Blog">
    SELECT * FROM BLOG
    <where>
        <if test="state != null">
            state = #{state}
        </if>
        <if test="title != null">
            AND title like #{title}
        </if>
        <if test="author != null and author.name != null">
            AND author_name like #{author.name}
        </if>
    </where>
</select>

where 元素只会在⼦元素返回任何内容的情况下才插⼊ “WHERE” ⼦句。⽽且,若⼦句的开头为”AND”或”OR”,where 元素也会将它们去除。

如果 where 元素与你期望的不太⼀样,可以通过⾃定义 trim 元素来定制 where 元素的功能。⽐如,和 where 元素等价的⾃定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
    ...
</trim>

prefixOverrides 属性会忽略通过管道符分隔的⽂本序列(注意此例中的空格是必要的)。上述例⼦会移除所有 prefixOverrides 属性中指定的内容,并且插⼊ prefix 属性中指定的内容。

⽤于动态更新语句的类似解决⽅案叫做 set。set 元素可以⽤于动态包含需要更新的列,忽略其它不更新的列。

<update id="updateAuthorIfNecessary">
    update Author
    <set>
        <if test="username != null">username=#{username},</if>
        <if test="password != null">password=#{password},</if>
        <if test="email != null">email=#{email},</if>
        <if test="bio != null">bio=#{bio}</if>
    </set>
    where id=#{id}
</update>

这个例⼦中,set 元素会动态地在⾏⾸插⼊ SET 关键字,并会删掉额外的逗号(这些逗号是在使⽤条件语句给列赋值时引⼊的)。与set元素等价的自定义trim元素:

<trim prefix="SET" suffixOverrides=",">
    ...
</trim>

覆盖了后缀值设置,并且⾃定义了前缀值。

foreach

对集合进行遍历构建SQL语句(尤其IN条件语句时)。

<select id="selectPostIn" resultType="domain.blog.Post">
    SELECT *
    FROM POST P
    WHERE ID in
    <foreach item="item" index="index" collection="list"
        open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

foreach 元素的功能⾮常强⼤,允许指定⼀个集合,声明可以在元素体内使⽤的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。

动态执行原理

  1. SqlResource:sql对象的来源,通过该接口可以获取sql对象。其唯一实现类是XmlSqlResource,表示通过xml文件生成sql对象。
  2. Sql:生成sql语句和获取sql相关的上下文环境(如ParameterMap,ResultMap等),三个实现类
    1. RawSql:原生sql语句,在初始化即可确定的sql语句
    2. SimpleDynamicSql:简单的动态sql,即sql语句中参数通过$property$方式指定,参数在sql生成过程会被替换,不作为sql执行参数
    3. DynamicSql:动态sql,即sql描述文件中包含isNotNull,isGreaterThan等条件标签
  3. SqlChild:表示sql抽象语法树的一个节点,包含sql语句的片段信息。两个实现类。每条动态sql通过SqlTag和SqlText构成相应的抽象语法树。
    1. SqlTag:表示动态sql片段,即配置文件中有一个动态标签,内含动态sql属性值(如prepend、property值等)。
    2. SqlText:表示静态sql⽚段,即为原⽣的sql语句。
  4. SqlTagHandler:该接⼝表示SqlTag(即不同的动态标签)对应的处理⽅式。⽐如实现类IsEmptyTagHandler⽤于处理isEmpty标签,IsEqualTagHandler⽤于处理isEqual标签。
  5. SqlTagContext:⽤于解释sql抽象语法树时使⽤的上下⽂环境。通过解释语法树每个节点,将⽣成的sql存⼊SqlTagContext。最终通过SqlTagContext获取完整的sql语句。

总结

涉及三个模式

  1. 解析器模式:初始化过程中构建出抽象语法树,请求处理时根据参数对象解释语法树,⽣成sql语句。
  2. 工厂模式:为动态标签的处理⽅式创建⼯⼚类(SqlTagHandlerFactory),根据标签名称获取对应的处理⽅式。
  3. 策略模式:将动态标签处理⽅式抽象为接⼝,针对不同标签有相应的实现类。解释抽象语法树时,定义统⼀的解释流程,再调⽤标签对应的处理⽅式完成解释中的各个⼦环节。

评论