Mybatis强大特性之一就是动态sql,可以在xml映射文件里面,以标签的形式动态的拼接sql语句,完成逻辑判断和动态拼接sql的功能。

元素 作用 备注
if 判断语句 单条件分支判断
choose、when、otherwise 相当于java的case when 多条件分支判断
Trim、where、set 辅助元素 用于处理sql拼装问题
foreach 循环语句 在in语句等列举条件常用,常用于实现批量操作



1.if标签


案例:ZJJ_Mybatis_2019/09/29_23:17:58_aspdq

if标签有一个必填的属性test,test的属性值符合ognl要求的判断表达式,表达式的结果可以是true或false,除此之外所有的非0值都是true,只有0为false

1. 判断条件property !=null 或者property == null
适用于任何类型的字眼,用于判断属性值是否为空,
2. 判断条件property != ‘’ 或者 property ==’’
仅适用于String类型的字段,用于判断是否为空字符串.
3. and和or
当有多个判断条件时候,使用and或or进行连接,嵌套的判断可以使用小括号分组,and相当于Java中的与(&&),or相当于Java中的或(||) .


需要注意一下就是一定要有个where标签或者 where 1=1,如果if条件都不满足的话最后生成的sql就会是以where结束,这样不符合sql规范就会报错.就上where1=1 或者where标签就可以杜绝这个问题.
还有and或者or都是需要手动添加的.不然也会不符合sql规范报错


2.foreach用法


ü foreach属性:
1. collection: 值是array 或者是list ,是什么值写你传递进来的就行了
2. item: 变量名,值为从迭代对象中取出来的每一个值
3. index: 索引的属性名,在集合数组的情况下值为当前索引值,当迭代循环的对象是Map类型时候,这个值为Map的key(键值).
4. open: 整合循环内容开头的字符串 , 比如说左括号
5. close : 整个循环内容结尾的字符串, 比如说右括号
6. separator : 每次循环的分隔符.

在in语句里面取出内容案例:ZJJMybatis_2019/09/30 1:43:07_3jew4


<select id=”selectMenuByRoleNameList” parameterType=”list” resultType=”CamelKeyMap”> SELECT DISTINCT c.menu_name, c.url, sort
FROM water_role a
LEFT JOIN water_role_menu b ON a.id = b.role_id
LEFT JOIN water_menu c ON c.id = b.menu_id
WHERE a.role_name IN
/item 2.变量名,值为从迭代对象中取出来的每一个值/
<foreach collection=”list” open=”(“ close=”)” item=”userName” separator=”,”> #{userName}
</foreach> ORDER BY sort
</select>



在in语句等列举条件常用,常用于实现批量操作

sql语句中有时候会使用in关键字,列如 id in(1,2,3),可以使用 ${ids}方式直接获取值,但是这种写法不能防止sql注入,想要避免sql注入就需要使用#{}的方式,这时候就要配合使用foreach标签来满足需求了.
foreach可以对数组,Map或者实现了Iterable接口(如List,set)的对象进行遍历,数组在处理的时候会转化成list对象,
因此foreach遍历的对象可以分为两大类:
iterable类型和Map类型.

ü foreach使用场景:
1. 前端传过来多个查询条件数据

比如 in (1,2,3)

2.批量新增



形参参数问题

只有一个数组参数或集合参数
当参数类型为集合的时候,默认会转换为Map类型,并添加一个key为collection的值(Mybatis3.3.0版本中增加),如果参数类型是List集合,那么就继续添加一个key为list的值(mybatis3.2.8及低版本中只有这一个key),这样,当collection=”list”时,就能得到这个集合,并对它进行循环操作.

当参数类型为数组的时候,也会转换成Map类型,默认的key为array,当采用如下的方法使用数组参数时,就需要把foreach标签中的collection属性值设置为array
List selectByIdList(Long[] idArray);
上面提到的是数组或者集合类型的参数默认的名字,推荐使用@Param来指定参数的名字,这时,collection就设置为通过@Param注解指定的名字.

ü 有多个参数
当有多个参数的时候,要使用@Param注解给每个参数都指定一个名字,否则在Sql中使用参数时就会不方便,因此将collection设置为@Param注解指定的名字即可.

ü 参数是map类型
使用Map和使用@Param注解方式类似,将collection指定为对应的Map中的key即可,如果要循环所传入的Map,推荐使用@Param注解指定名字,此时可以将collection设置为指定的名字,如果不想指定名字,就使用默认值 _parameter

ü 参数是一个对象
这个情况下指定为对象的属性名即可.当使用对象内多层嵌套的对象时,使用属性.属性(集合和数组可以使用下标取值)的方式可以指定深层的属性值.

批量插入

批量插入(能获取批量插入的id)
案例:ZJJMybatis_2019/09/30 1:50:21_fz0mk


批量插入有两种方式:
1. 通过foreach动态拼装SQL语句
2. 使用BATCH类型的excutor,

ü 多少条数据执行一次批量插入呢
字段比较多的表就几百条提交一次,如果字段占用比较少,比如就三五个字段,那么一万条提交一次也是可以的
具体也要看业务场景,但是同时还需要思考占用内存问题(数据积压在集合里面是占用内存的)

实现动态update(foreach)


当参数类型是Map的时候, foreach 如何实现动态update.
当参数是map类型的时候,foreach标签的index属性值对应的不是索引值,而是map中的key,利用这个key可以使用动态update.
现在需要通过制定的列名和对应的值去更新数据,实现代码如下


dao接口
int updateByMap(Map map);

daomapper
<update id=”updateByMap”>
update sysuser
set
<foreach collection=”_parameter” item=”val” index=”key” separator=”,”>
${key} = #{val}
</foreach>
where id = #{id}
</update>
动态SQL - 图1
测试类
@Test
public void testUpdateByMap(){
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//从数据库查询 1 个 user 对象
Map map = new HashMap();
map.put(“id”, 1L);
map.put(“user_email”, “test@mybatis.tk”);
map.put(“user_password”, “12345678”);
//更新数据
userMapper.updateByMap(map);
//根据当前 id 查询修改后的数据
SysUser user = userMapper.selectById(1L);
Assert._assertEquals
(“test@mybatis.tk”, user.getUserEmail());
} finally {
//为了不影响数据库中的数据导致其他测试失败,这里选择回滚
sqlSession.rollback();
//不要忘记关闭 sqlSession
sqlSession.close();
}
}

3.bind的用法

动态SQL - 图2
动态SQL - 图3

4.动态新增

案例:ZJJ_Mybatis_2019/09/29_23:51:55_3z7na


在数据库表中插入数据时候,如果某一列的参数值不为空,就使用传入的值,如果传入参数为空,就使用数据库中的默认值(默认值通常是空),而不使用传入的空值,使用if就可以实现动态插入列的功能
<insert id=”insert2” useGeneratedKeys=”true” keyProperty=”id”>
insert into sys_user(
user_name, user_password,
<if test=”userEmail != null”>
<if test=”userEmail != ‘’”>
user_email,
</if>
</if>
user_info, head_img, create_time)
values(
#{userName}, #{userPassword},
<if test=”userEmail != null”>
<if test=”userEmail != ‘’”>
#{userEmail},
</if>
</if>
#{userInfo}, #{headImg, jdbcType=BLOB}, #{createTime, jdbcType=TIMESTAMP})
</insert>

//*
<insert id=”insertSelective” parameterType=”com.pinyougou.pojo.TbGoodsDesc” >
insert into tb_goods_desc
<trim prefix=”(“ suffix=”)” suffixOverrides=”,” >
<if test=”goodsId != null” >
goods_id,
</if>
<if test=”introduction != null” >
introduction,
</if>
<if test=”specificationItems != null” >
specification_items,
</if>
<if test=”customAttributeItems != null” >
custom_attribute_items,
</if>
<if test=”itemImages != null” >
item_images,
</if>
<if test=”packageList != null” >
package_list,
</if>
<if test=”saleService != null” >
sale_service,
</if>
</trim>
<trim prefix=”values (“ suffix=”)” suffixOverrides=”,” >
<if test=”goodsId != null” >
#{goodsId,jdbcType=BIGINT},
</if>
<if test=”introduction != null” >
#{introduction,jdbcType=VARCHAR},
</if>
<if test=”specificationItems != null” >
#{specificationItems,jdbcType=VARCHAR},
</if>
<if test=”customAttributeItems != null” >
#{customAttributeItems,jdbcType=VARCHAR},
</if>
<if test=”itemImages != null” >
#{itemImages,jdbcType=VARCHAR},
</if>
<if test=”packageList != null” >
#{packageList,jdbcType=VARCHAR},
</if>
<if test=”saleService != null” >
#{saleService,jdbcType=VARCHAR},
</if>
</trim>
</insert>


ü trim标签作用

where标签和set标签的功能都可以用trim标签来实现,并且在底层就是通过TrimSqlNode实现的.

where标签对应trim的实现如下




提示:
这里的AND和OR后面的空格不能省略,为了避免匹配到andes,orders等单词.


ü trim标签有如下的属性
prefix:当trim元素内包含内容时,会给内容增加prefix指定的前缀.
prefixOverrides:当trim元素内包含内容时,会把内容中匹配的前缀字符串去掉,
suffix: 当trim元素内包含内容时,会给内容增加suffix指定的后缀.
suffixOverrides: 当trim元素内包含内容时,会把内容中匹配的后缀字符串去掉/


5.动态修改

案例:ZJJ_Mybatis_2019/09/29_23:46:46_2alff

<update id=”updateByIdSelective”> update sys_user
<set> <if test=”userName != null and userName != ‘’”> user_name = #{userName},
</if> <if test=”userInfo != null and userInfo != ‘’”> user_info = #{userInfo},
</if> <if test=”headImg != null”> head_img = #{headImg, jdbcType=BLOB},
</if> <if test=”createTime != null”> create_time = #{createTime, jdbcType=TIMESTAMP},
</if> id = #{id},
</set> where id = #{id}
</update>



ü set标签作用
如果该标签包含的元素中有返回值,就插入一个set,如果set后面的字符串是以逗号为结尾的,就讲这个逗号去掉.


只更新有变化的字段,需要注意,更新的时候不能将原来有值但是没有发生变化的字段更新为空或者null,通过if标签可以实现这种动态列更新.

要结合业务层的逻辑判断,确保最终产生的sql语句没有语法错误,需要注意两点:
1. 每个if元素里面sql语句后面的逗号
2. where关键字前面的id=#{id} 这个条件

如果有id = #{id}这个条件,最终的sql如下:
update sys_user set id = #{id} where id = #{id} — sql是正确的
如果没有这个条件,最终的sql如下,
update sys_user set where id = #{id} — 这个sql很明显是错误的,set关键字后面没有内容,直接是where关键字,不符合语句规范

查询条件只有一个不是null也不是空(假设是userName)
如果有id = #{id}这个条件,最终的sql如下.
update sys_user set user_name = #{userName} , id = #{id} where id = #{id}
如果没有这个条件,最终的sql如下.
update sys_user set user_name = #{userName} , where id = #{id} — where关键字前面直接就是一个逗号,这个sql语句也是错误的.

从上面两种情况来看,id = #{id} 这个条件可以最大限度保证方法不出错,除了使用这种方式外,还可以结合业务层的逻辑判断调整xml文件中的sql来确保最终的sql语句的正确性,也可以通过where和set标签来解决这些问题.


6.动态查询

案例:ZJJ_Mybatis_2019/09/29_23:17:58_aspdq


ü where标签作用
where标签作用是,
1. 如果标签内的 if 条件满足了,就会在判断条件前面添加where 关键字
2. 把第一个标签的and或者or去掉.

<select id=”selectIfandWhereOper” resultMap=”BaseResultMap”> select
<include refid=”Base_Column_List”/> from t_user a
<where>
<if test=”email != null and email != ‘’”> and a.email like CONCAT(‘%’, #{email}, ‘%’)
</if>
<if test=”sex != null “> and a.sex = #{sex}
</if>
</where>
</select>


当if条件都不满足的时候,where元素中没有内容,所以在sql中不会出现where标签.



Oracle的案例
判断的是判断的传入的参数 带where标签的
<select id=”conditionSearchByPlanVo” parameterType=”io.mergency.entity.Vo.PlanVo”
resultMap=”emergencyPlanMap”>
select _* _from EMERGENCY_PLAN

  1. <**where**><br /> <**if test="gradeId != null and gradeId != ''"**><br /> and GRADE_ID = #{gradeId}<br /> </**if**>
  2. <**if test="name != null and name != ''"**><br /> and name LIKE '%${name}%'<br /> </**if**><br /> <**if test="entryTime != null and entryTime != ''"**><br /> and ENTRY_TIME between<br /> TO_DATE(#{startTime},'yyyy-mm-dd hh24:mi:ss')<br /> and<br /> TO_DATE(#{endTime},'yyyy-mm-dd hh24:mi:ss')<br /> </**if**>
  3. </**where**>

</select>
也可以去掉where标签 直接在后面 where 1 = 1
<select id=”getAPPAdvertisingDetailList” resultMap=”APPAdvertisingDetailMap” >
select ba.*, bu.NAME as userName, bu.id as userId
from B_ADVERTISEMENT_INFO ba
inner join B_USER bu on ba.USER_ID = bu.ID
<if test=”effectSign != null and effectSign != ‘’”>
and ba.EFFECT_SIGN = #{effectSign}
</if>

  1. <**if test="userName != null and userName != ''"**><br /> and bu.NAME like '%${userName}%'<br /> </**if**><br /> <**if test="advertisementSign != null and advertisementSign != ''"**><br /> and ba.ADVERTISEMENT_SIGN = #{advertisementSign}<br /> </**if**><br /> where 1 = 1<br /></**select**><br /> <br /> <br /> <br />

if else查询

choose的用法
动态SQL - 图4
动态SQL - 图5
ü 测试类
@Test
public void testSelectByIdOrUserName(){
SqlSession sqlSession = getSqlSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//只查询用户名时
SysUser query = new SysUser();
query.setId(1L);
query.setUserName(“admin”);
SysUser user = userMapper.selectByIdOrUserName(query);
Assert.assertNotNull(user);
//当没有 id 时
query.setId(null);
user = userMapper.selectByIdOrUserName(query);
Assert.assertNotNull(user);
//当 id 和 name 都为空时
query.setUserName(null);
user = userMapper.selectByIdOrUserName(query);
Assert.assertNull(user);
} finally {
//不要忘记关闭 sqlSession
sqlSession.close();
}
}
ü dao接口
/ 根据用户 id 或用户名查询 */
SysUser selectByIdOrUserName(SysUser sysUser);
ü
ü daoxml文件
<
select id=”selectByIdOrUserName” resultType=”tk.mybatis.simple.model.SysUser”>
select id,
user_name userName,
user_password userPassword,
user_email userEmail,
user_info userInfo,
head_img headImg,
create_time createTime
from sys_user
where 1 = 1
<
choose>
<
when test=”id != null”>
and id = #{id}
</
when>
<
when test=”userName != null and userName != ‘’”>
and user_name = #{userName}
</
when>
<
otherwise>
limit 0
</
otherwise>
</
choose>
</
select**>

————————————————

  1. <select id="queryRegionShoppingGuideDetail2" resultType="com.feihe.vo.form.sales.GDDetailListVO">
  2. SELECT regionInfo.lev3_name AS province,
  3. regionInfo.lev4_name AS district,
  4. regionInfo.lev5_name AS area,
  5. br.shop_name AS shopName,
  6. br.shop_id AS shopId,
  7. br.shop_code AS shopCode,
  8. br.guide_code AS guideCode,
  9. br.account_id AS accountId,
  10. br.account_month AS accountMonth,
  11. br.guide_id AS guideId,
  12. br.guide_name AS guideName,
  13. br.scan_sale_count AS scanSaleCount,
  14. br.nature_sale_count AS natureSaleCount,
  15. cast(br.sale_price AS CHAR) AS saleOrEntryMoney
  16. FROM t_form_sale_br_month_report br
  17. LEFT JOIN t_organization_region_info t2 ON br.fk_region_id = t2.pk_org_region_id
  18. LEFT JOIN t_organization_region_info regionInfo ON regionInfo.pk_org_region_id = br.fk_region_id
  19. <where>
  20. br.account_month = #{params.accountMonth}
  21. AND br.is_delete = 0
  22. <choose>
  23. <when test="dmRegionIds != null and dmRegionIds.size() > 0 and pmRegionIds != null and pmRegionIds.size() > 0">
  24. AND (
  25. t2.lev4_code IN
  26. <foreach collection="dmRegionIds" open="(" close=")" item="item" separator=",">
  27. #{item}
  28. </foreach>
  29. OR t2.lev3_code IN
  30. <foreach collection="pmRegionIds" open="(" close=")" item="item" separator=",">
  31. #{item}
  32. </foreach>
  33. )
  34. </when>
  35. <otherwise>
  36. <if test="dmRegionIds != null and dmRegionIds.size() > 0">
  37. AND t2.lev4_code IN
  38. <foreach collection="dmRegionIds" open="(" close=")" item="item" separator=",">
  39. #{item}
  40. </foreach>
  41. </if>
  42. <if test="pmRegionIds != null and pmRegionIds.size() > 0">
  43. AND t2.lev3_code IN
  44. <foreach collection="pmRegionIds" open="(" close=")" item="item" separator=",">
  45. #{item}
  46. </foreach>
  47. </if>
  48. </otherwise>
  49. </choose>
  50. <if test="params.guideCode != null and params.guideCode != ''">
  51. AND br.guide_code like concat(#{params.guideCode}, '%')
  52. </if>
  53. <if test="params.guideName != null and params.guideName != ''">
  54. AND br.guide_name like concat(#{params.guideName}, '%')
  55. </if>
  56. AND t2.`level` = 5
  57. ORDER BY br.scan_sale_count DESC
  58. </where>
  59. </select>