MyBatis 的强大特性之一便是它的动态 SQL。

如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

通常使用动态 SQL 不可能是独立的一部分,MyBatis 当然使用一种强大的动态 SQL 语言来改进这种情形,这种语言可以被用在任意的 SQL 映射语句中。

动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多的元素需要来了解。MyBatis 3 大大提升了它们,现在用不到原先一半的元素就可以了。MyBatis 采用功能强大的基于 OGNL 的表达式来消除其他元素。

文章最下面包含一些demo.

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

if

动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分。比如:


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

这条语句提供了一个可选的文本查找类型的功能。如果没有传入“title”,那么所有处于“ACTIVE”状态的BLOG都会返回;反之若传入了“title”,那么就会把模糊查找“title”内容的BLOG结果返回(就这个例子而言,细心的读者会发现其中的参数值是可以包含一些掩码或通配符的)。

如果想可选地通过“title”和“author”两个条件搜索该怎么办呢?首先,改变语句的名称让它更具实际意义;然后只要加入另一个条件即可。

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

choose, when, otherwise

有些时候,我们不想用到所有的条件语句,而只想从中择其一二。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是上面的例子,但是这次变为提供了“title”就按“title”查找,提供了“author”就按“author”查找,若两者都没有提供,就返回所有符合条件的BLOG(实际情况可能是由管理员按一定策略选出BLOG列表,而不是返回大量无意义的随机结果)。

<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

前面几个例子已经合宜地解决了一个臭名昭著的动态 SQL 问题。现在考虑回到“if”示例,这次我们将“ACTIVE = 1”也设置成动态的条件,看看会发生什么。

<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>
</select>

如果这些条件没有一个能匹配上将会怎样?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果仅仅第二个条件匹配又会怎样?这条 SQL 最终会是这样:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

这个查询也会失败。这个问题不能简单的用条件句式来解决,如果你也曾经被迫这样写过,那么你很可能从此以后都不想再这样去写了。

MyBatis 有一个简单的处理,这在90%的情况下都会有用。而在不能使用的地方,你可以自定义处理方式来令其正常工作。一处简单的修改就能得到想要的效果:

<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 元素知道只有在一个以上的if条件有值的情况下才去插入“WHERE”子句。而且,若最后的内容是“AND”或“OR”开头的,where 元素也知道如何将他们去除。

如果 where 元素没有按正常套路出牌,我们还是可以通过自定义 trim 元素来定制我们想要的功能。比如,和 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 关键字,同时也会消除无关的逗号,因为用了条件语句之后很可能就会在生成的赋值语句的后面留下这些逗号。

若你对等价的自定义 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 元素的功能是非常强大的,它允许你指定一个集合,声明可以用在元素体内的集合项和索引变量。它也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。

注意 You can pass any Iterable object (for example List, Set, etc.), as well as any Map or Array object to foreach as collection parameter. When using an Iterable or Array, index will be the number of current iteration and value item will be the element retrieved in this iteration. When using a Map (or Collection of Map.Entry objects), index will be the key object and item will be the value object.

到此我们已经完成了涉及 XML 配置文件和 XML 映射文件的讨论。下一部分将详细探讨 Java API,这样才能从已创建的映射中获取最大利益。

bind

bind 元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。比如:

<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>

Multi-db vendor support

一个配置了“_databaseId”变量的 databaseIdProvider 对于动态代码来说是可用的,这样就可以根据不同的数据库厂商构建特定的语句。比如下面的例子:

<insert id="insert">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
<if test="_databaseId == 'oracle'">
select seq_users.nextval from dual
</if>
<if test="_databaseId == 'db2'">
select nextval for seq_users from sysibm.sysdummy1"
</if>
</selectKey>
insert into users values (#{id}, #{name})
</insert>

动态 SQL 中可插拔的脚本语言

MyBatis 从 3.2 开始支持可插拔的脚本语言,因此你可以在插入一种语言的驱动(language driver)之后来写基于这种语言的动态 SQL 查询。

可以通过实现下面接口的方式来插入一种语言:

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

一旦有了自定义的语言驱动,你就可以在 mybatis-config.xml 文件中将它设置为默认语言:

<typeAliases>
<typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
<setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>

除了设置默认语言,你也可以针对特殊的语句指定特定语言,这可以通过如下的 lang 属性来完成:

<select id="selectBlog" lang="myLanguage">
SELECT * FROM BLOG
</select>

或者在你正在使用的映射中加上注解 @Lang 来完成:

public interface Mapper {
@Lang(MyLanguageDriver.class)
@Select("SELECT * FROM BLOG")
List<Blog> selectBlog();
}

更多参见Mybatis官网 http://mybatis.github.io/mybatis-3/zh/index.html

例子:
 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"> <mapper namespace="cn.ffcs.zone.detailType.persistent.DetailTypeMapper">
<resultMap id="BaseResultMap" type="cn.ffcs.zone.detailType.domain.DetailType">
<result property="id" column="ID" />
<result property="talentId" column="TALENT_ID" />
<result property="typeCode" column="TYPE_CODE" />
</resultMap> <select id="findDetailTypes" resultMap="BaseResultMap">
select ID , TYPE_CODE , TALENT_ID
from APP_ZONE_USER_TALENT_TYPE
</select> <insert id="saveEntity">
<selectKey keyProperty="id" resultType="long" order="BEFORE">
<![CDATA[
select SEQ_TALENT_TYPE_ID.NEXTVAL from dual
]]>
</selectKey> INSERT INTO APP_ZONE_USER_TALENT_TYPE(
ID,
TALENT_ID,
TYPE_CODE
) VALUES (
#{id,jdbcType=BIGINT},
#{talentId,jdbcType=BIGINT},
#{typeCode,jdbcType=VARCHAR}
)
</insert> <insert id="svaeTalentTypeBatch" parameterType="list">
INSERT INTO APP_ZONE_USER_TALENT_TYPE
(
ID,TALENT_ID,TYPE_CODE
)
SELECT SEQ_TALENT_TYPE_ID.NEXTVAL,t.* from
(
<foreach collection="list" item="item" index="index" separator="union all">
select
#{item.talentId,jdbcType=BIGINT},
#{item.typeCode,jdbcType=VARCHAR}
from dual
</foreach>
) t
</insert> <delete id="delDetailType">
delete from APP_ZONE_USER_TALENT_TYPE where ID IN
<foreach collection="ids" item="item" index="index" open="(" separator="," close=")">
#{item}
</foreach>
</delete>
</mapper>
 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"> <mapper namespace="cn.ffcs.zone.userCard.persistent.UserCardMapper">
<resultMap id="BaseResultMap" type="cn.ffcs.zone.userCard.domain.UserCard">
<result property="id" column="ID" />
<result property="userId" column="USER_ID" />
<result property="cardNo" column="CARD_NO" />
<result property="cardType" column="CARD_TYPE" />
<result property="cyUserId" column="CY_USER_ID" />
<result property="createdt" column="CREATEDT" />
<result property="updatedt" column="UPDATEDT" />
<result property="cardTypeDefault" column="CARD_TYPE_DEFAULT" />
</resultMap> <sql id="columns">
<![CDATA[
ID,
USER_ID,
CARD_NO,
CARD_TYPE,
CY_USER_ID,
CREATEDT,
UPDATEDT,
CARD_TYPE_DEFAULT
]]>
</sql> <insert id="saveUserCard">
<selectKey keyProperty="id" resultType="long" order="BEFORE">
<![CDATA[
select SEQ_AZ_USER_CARD_ID.NEXTVAL from dual
]]>
</selectKey>
INSERT INTO APP_ZONE_USER_CARD (
ID,
USER_ID,
CARD_NO,
CARD_TYPE,
CY_USER_ID,
CREATEDT,
UPDATEDT,
CARD_TYPE_DEFAULT
) VALUES (
#{id,jdbcType=BIGINT},
#{userId,jdbcType=BIGINT},
#{cardNo,jdbcType=VARCHAR},
#{cardType,jdbcType=VARCHAR},
#{cyUserId,jdbcType=VARCHAR},
sysdate,
sysdate,
#{cardTypeDefault,jdbcType=VARCHAR}
)
</insert> <update id="updateUserCardById">
update APP_ZONE_USER_CARD set
<if test="@Ognl@isNotNull(cardType)">
CARD_TYPE= #{cardType,jdbcType=VARCHAR},
</if>
<if test="@Ognl@isNotNull(cardNo)"> CARD_NO= #{cardNo,jdbcType=VARCHAR},</if>
<if test="@Ognl@isNotNull(cardTypeDefault)"> CARD_TYPE_DEFAULT= #{cardTypeDefault,jdbcType=BIGINT},</if>
UPDATEDT=sysdate
where ID= #{id,jdbcType=BIGINT}
</update> <update id="updateByUserId">
update APP_ZONE_USER_CARD set
<if test="@Ognl@isNotNull(cardType)">
CARD_TYPE= #{cardType,jdbcType=VARCHAR},
</if>
<if test="@Ognl@isNotNull(cardNo)"> CARD_NO= #{cardNo,jdbcType=VARCHAR},</if>
UPDATEDT=sysdate
where USER_ID= #{userId,jdbcType=BIGINT}
<if test="cardType == 2">
and CARD_TYPE = 2
</if>
</update> <update id="updateByCyUserId">
update APP_ZONE_USER_CARD set
<if test="@Ognl@isNotNull(cardType)">
CARD_TYPE= #{cardType,jdbcType=VARCHAR},
</if>
<if test="@Ognl@isNotNull(cardNo)"> CARD_NO= #{cardNo,jdbcType=VARCHAR},</if>
UPDATEDT=sysdate
where CY_USER_ID= #{cyUserId,jdbcType=VARCHAR}
</update> <select id="findByParam" resultMap="BaseResultMap">
select * from app_zone_user_card t where 1=1
<if test=" cardType!=null and cardType!=''">
and t.CARD_TYPE=#{cardType,jdbcType=VARCHAR}
</if>
<if test="cardNo!=null and cardNo!=''">
and t.CARD_NO= #{cardNo,jdbcType=VARCHAR}
</if>
<if test="userId!=null and userId!=''">
and t.USER_ID= #{userId,jdbcType=BIGINT}
</if>
<if test="cyUserId!=null and cyUserId!=''">
and t.CY_USER_ID= #{cyUserId,jdbcType=VARCHAR}
</if>
<if test="cardTypeDefault!=null and cardTypeDefault!=''">
and t.CARD_TYPE_DEFAULT= #{cardTypeDefault,jdbcType=BIGINT}
</if>
</select> </mapper>

java代码

package cn.ffcs.zone.userCard.persistent;

import java.util.List;
import java.util.Map; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Select; import cn.ffcs.mybatis.BaseMapper;
import cn.ffcs.zone.userCard.domain.UserCard; public interface UserCardMapper extends BaseMapper<UserCard>{ public int saveUserCard(UserCard uc); @Select(" select * from app_zone_user_card t where t.user_id=#{userId} ")
@ResultMap("BaseResultMap")
public UserCard findByUserId(@Param("userId") Long userId); @Select(" select * from app_zone_user_card t where t.id=#{id} ")
@ResultMap("BaseResultMap")
public UserCard findById(@Param("id") Long id); //根据 用户id 获取卡号信息
@Select(" select * from app_zone_user_card t where t.CY_USER_ID=#{cjUserId} ")
@ResultMap("BaseResultMap")
public UserCard findByCyId(@Param("cjUserId") String cjUserId); //根据主键id 更新
public int updateUserCardById(UserCard uc); //根据用户id 更新
public int updateByUserId(UserCard uc); //根据创寓 用户id 更新
public int updateByCyUserId(UserCard uc); public List<UserCard> findByParam(Map<String, Object> param); }

  

 package cn.ffcs.mybatis;

 import java.io.Serializable;
import java.util.List;
import java.util.Map; import org.apache.ibatis.session.RowBounds; public interface BaseMapper<T> { public int saveEntity(T entity); public int updateEntity(T entity); public T findByPK(Serializable entityId); public long findCountByCriteria(Map<String, Object> param); public List<T> findPageListByCriteria(Map<String, Object> para, RowBounds bounds); public int deleteByPKArray(Serializable[] ids);
}

BaseMapper

Mybatis-动态 SQL的更多相关文章

  1. mybatis实战教程(mybatis in action)之八:mybatis 动态sql语句

    mybatis 的动态sql语句是基于OGNL表达式的.可以方便的在 sql 语句中实现某些逻辑. 总体说来mybatis 动态SQL 语句主要有以下几类:1. if 语句 (简单的条件判断)2. c ...

  2. 9.mybatis动态SQL标签的用法

    mybatis动态SQL标签的用法   动态 SQL MyBatis 的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么 ...

  3. 自己动手实现mybatis动态sql

    发现要坚持写博客真的是一件很困难的事情,各种原因都会导致顾不上博客.本来打算写自己动手实现orm,看看时间,还是先实现一个动态sql,下次有时间再补上orm完整的实现吧. 用过mybatis的人,估计 ...

  4. Mybatis动态SQL单一基础类型参数用if标签

    Mybatis动态SQL单一基础类型参数用if标签时,test中应该用 _parameter,如: 1 2 3 4 5 6 <select id="selectByName" ...

  5. 超全MyBatis动态SQL详解!( 看完SQL爽多了)

    MyBatis 令人喜欢的一大特性就是动态 SQL. 在使用 JDBC 的过程中, 根据条件进行 SQL 的拼接是很麻烦且很容易出错的. MyBatis 动态 SQL 的出现, 解决了这个麻烦. My ...

  6. Mybatis动态SQL简单了解 Mybatis简介(四)

    动态SQL概况 MyBatis 的强大特性之一便是它的动态 SQL 在Java开发中经常遇到条件判断,比如: if(x>0){ //执行一些逻辑........ }   Mybatis应用中,S ...

  7. mybatis原理分析学习记录,mybatis动态sql学习记录

    以下个人学习笔记,仅供参考,欢迎指正. MyBatis 是支持定制化 SQL.存储过程以及高级映射的持久层框架,其主要就完成2件事情: 封装JDBC操作 利用反射打通Java类与SQL语句之间的相互转 ...

  8. mybatis 动态sql和参数

    mybatis 动态sql 名词解析 OGNL表达式 OGNL,全称为Object-Graph Navigation Language,它是一个功能强大的表达式语言,用来获取和设置Java对象的属性, ...

  9. MyBatis动态SQL之一使用 if 标签和 choose标签

    bootstrap react https://segmentfault.com/a/1190000010383464 xml 中 < 转义 to thi tha <if test=&qu ...

  10. MyBatis动态SQL(认真看看, 以后写SQL就爽多了)

    目录 0 一起来学习 mybatis 1 数据准备 2 if 标签 2.1 在 WHERE 条件中使用 if 标签 2.1.1 查询条件 2.1.2 动态 SQL 2.1.3 测试 2.2 在 UPD ...

随机推荐

  1. Double Buffering Windows Forms

    Double Buffering Windows Forms As much as we would like it not to be the case, graphics can be slow ...

  2. Determining Current Block and Current Item in Oracle Forms

    SYSTEM.CURSOR_BLOCK Determining current block in Oracle Forms Using SYSTEM.CURSOR_BLOCK system varia ...

  3. touch穿透

    出现穿透的前提:上层绑定touch事件,下层绑定click事件. 解决方案:touchend绑定: e.preventDefault();$(下层).css("pointer-events& ...

  4. window.location.href 失效的解决办法

    第一种:在window.location.href 后面加上 window.event.returnValue = false; 如: <a href="#" onclick ...

  5. 了解less跟sass

    less官网:http://www.bootcss.com/p/lesscss/ LESS编写技巧分享:http://www.xuanfengge.com/less-writing-skills-sh ...

  6. 关于JSPatch热修复

    今天和同事聊到JSPatch热修复,我们来看下JSPatch,确实解决了APP升级的痛点. 刚好,已经有这么一个第三方平台实现了后台管理,全套服务(网址是:http://jspatch.com/),先 ...

  7. SUBLIME TEXT 2 设置文件详解

    SUBLIME TEXT 2 设置文件详解 Preferences.sublime-settings文件: // While you can edit this file, it’s best to ...

  8. iOS沙盒路径的查看和使用

    1.模拟器沙盒目录 文件都在个人用户名文件夹下的一个隐藏文件夹里,中文叫资源库,他的目录其实是Library. 因为应用是在沙箱(sandbox)中的,在文件读写权限上受到限制,只能在几个目录下读写文 ...

  9. 关于H5中自定义属性的设置和获取

    自定义数据属性是在HTML5中新加入的一个特性.简单来说,自定义数据属性规范规定任何以data-开头属性名并且赋值.自定义数据属性是为了保存页面或者应用程序的私有自定义数据,这些自定义数据属性保存进D ...

  10. sql server 自增长id 允许插入显示值

    --允许插入显示插入主键id的值SET IDENTITY_INSERT [T0002_SType] ON 执行insert插入语句------------------ --关闭 插入显示值SET ID ...