前言
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
例如在注册用户的时候, 提交的表单中肯定有非必填字段, 前端会将拿到的数据送给后端, 但是, 用户在提交的时候, 由于存在非必填字段, 于是用户提交的字段可能是变数, 可能是一个, 两个或者三个, 例如有下面三个字段
- username
- password
- tele
- age(非必填)
- sex
- .....
除了age都为必填, 这个时候, 前端返回数据给后端, 然后使用sql将其存入数据库, 那么, 在插入的时候
insert into table(username, password, tele, sex, age) values(#{username},#{username}, #{tele}, #{sex}, #{age});
上面是age传入的时候, 那么试想一下, 如果这个age如果用户没有选择填写呢, 那么insert语句中的age选项就应该被抹除
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
if
<insert id="insert" parameterType="org.example.model.User" useGeneratedKey s="true" keyProperty="id"> insert into user( username, password, nickname, <if test="sex != null"> sex, </if> birthday, head ) values ( #{username}, #{password}, #{nickname}, <if test="sex != null"> #{sex}, </if> #{birthday}, #{head} ) </insert>
if中有一个test属性, test是判断这个sex项是否存在的标准, 前端传过来的数据会被解析为java对象, 并将属性写入到对象中, 如果对象中的sex属性为空, 那么就代表用户没有传入这个字段, 于是就需要将这个项给抹除.
trim
上面的情况是只有一个选填项, 但是如果所有的字段, 都可能是选填项, 那么就可以配合trim使用if来解决:
<insert id="insert" parameterType="org.example.model.User" useGeneratedKeys="true" keyProperty="id"> insert into user <trim prefix="(" suffix=")" suffixOverrides=","> <if test="username != null"> username, </if> <if test="password != null"> password, </if> <if test="nickname != null"> nickname, </if> <if test="sex != null"> sex, </if> <if test="birthday != null"> birthday, </if> <if test="head != null"> head, </if> <if test="createTime != null"> create_time, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="username != null"> #{username}, </if> <if test="password != null"> #{password}, </if> <if test="nickname != null"> #{nickname}, </if> <if test="sex != null"> #{sex}, </if> <if test="birthday != null"> #{birthday}, </if> <if test="head != null"> #{head}, </if> <if test="createTime != null"> #{createTime}, </if> </trim> </insert>
- prefix : 表示整个trim语句块的开头
- suffix : 表示整个trim语句块的结尾
- prefixOverrides : 表示整个语句块要去除掉的前缀
- suffixOverrides : 表示整个语句块中, 要去除掉的后缀, 这个后缀是不计suffix的. 例如trim语句块如下, 那么结束的时候就为 " .... .... head, create_time,)" ,呢么就会忽略 ')', 去除create_time后面的','
<if test="head != null"> head, </if> <if test="createTime != null"> create_time, </if>
上面语句翻译过来就是
isnert into user(username, password, age, sex, ) values(#{username}, #{password}, #{age}, #{sex},)
需要注意的是, 这里的sex可能为选填, 无论如何, 这个table_name后面的参数和values中的参数, 必定有以 ',' 结尾的内容, 但是这个在sql中是错误语句. 于是使用suffixOverrides将后缀','去除(不包括 suffix =')').
where
传⼊的⽤户对象,根据属性做 where 条件查询,⽤户对象中属性不为 null 的,都为查询条件。如user.username 为 "a",则查询条件为 where username="a":
UserMapper 接⼝中新增条件查询⽅法:
List<User> selectByCondition(User user);
xml中新增查询语句:
<select id="selectByCondition" parameterType="org.example.model.User" resultMap="BaseResultMap"> select id, username, password, nickname, sex, birthday, head, create_time from user <where> <if test="username != null"> and username=#{username} </if> <if test="password != null"> and password=#{password} </if> <if test="nickname != null"> and nickname=#{nickname} </if> <if test="sex != null"> and sex=#{sex} </if> <if test="birthday != null"> and birthday=#{birthday} </if> <if test="head != null"> and head=#{head} </if> <if test="createTime != null"> and create_time=#{createTime} </if> </where> </select>
注意: 这里的and一定要写到 column = #{column } 的前面, 也就是:
' and column = #{column } '
where标签就会自动识别并去除多余的and.
当然, where也可以使用trim标签和if标签代替.
set
根据传⼊的⽤户对象属性来更新⽤户数据,可以使⽤标签来指定动态内容
定义mapper接口:
int updateById(User user);
xml:
<update id="updateById" parameterType="org.example.model.User"> update user <set> <if test="username != null"> username=#{username}, </if> <if test="password != null"> password=#{password}, </if> <if test="nickname != null"> nickname=#{nickname}, </if> <if test="sex != null"> sex=#{sex}, </if> <if test="birthday != null"> birthday=#{birthday}, </if> <if test="head != null"> head=#{head}, </if> <if test="createTime != null"> create_time=#{createTime}, </if> </set> where id=#{id} </update>
set会自动去掉末尾的 ','
foreach
对集合进⾏遍历时可以使⽤该标签。标签有如下属性:
- collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象
- item:遍历时的每⼀个对象
- open:语句块开头的字符串
- close:语句块结束的字符串
- separator:每次遍历之间间隔的字符串
示例:根据多个⽂章 id 来删除⽂章数据
ArticleMapper 中新增接⼝⽅法
int deleteByIds(List<Integer> ids);
使用List接收, Integer为泛型, 传入的是一个Integer的list集合, 每一个Integer对象对应一个要被删除的文章(Article)
ArticleMapper.xml 中新增删除 sql:
<delete id="deleteByIds"> delete from article where id in <foreach collection="list" item="item" open="(" close=")" separator=","> #{item} </foreach> </delete>
in后面其实是可以使用一个多表查询的, 现在这里使用的foreach进行遍历.
- Collection指明集合的类型
- itme表示每个每一个对象
然后就遍历成了:
delete from article where id in (item1, item2, item3 ..... )
这一个个item对象就是list中的Integer对象. 也就是对应的int类型, 与id的类型对应.