*****
# 4.1 if用法
**if标签的作用:**
* 通过判断参数值来决定是否使用某个查询条件。
* 用于UPDATE语句中判断是否更新某一个字段。
* 在INSERT语句中用来判断是否插入某个字段的值。
## 4.1.1 在WHERE条件中使用if
例子: 实现一个用户管理高级查询功能,支持以下三种情况
1. 只根据用户名进行模糊查询。
2. 只根据邮箱进行完全匹配。
3. 根据用户名和邮箱查询匹配的用户。
**不使用动态sql会出现的问题:**
![](https://box.kancloud.cn/dff6ca4125e9db57a1db85158aeae297_540x344.png)
当只提供userName的参数值a时, userEmail默认是null,这时会查userName=a and email=null的用户,查询结果不正确。
**使用动态sql**
![](https://box.kancloud.cn/017dd53e152c8758a5013ba437ab7aca_563x473.png)
test的属性值是一个表达式,表达式结果是true或false。对字符串的判断和Java中的判断类似, 首先需要判断字段是否为null, 然后再去判断是否为空。注意 SQL中where关键字后面的条件where 1=1由于两个条件都是动态的, 所以如果没有1=1这个默认条件, 当两个if判断都不满足时, 最后生成的SQL就会以where结束,这样不符合SQL规范报错。
## 4.1.2 在UPDATE更新列中使用
if单表更新使用if
## 4.1.3 在INSERT动态插入列中使用
if插入单表时使用if
# 4.2 choose用法
**choose元素介绍**
choose元素中包含when和otherwise两个标签, 一个choose中至少有一个或多个when, 有0个或者1个otherwise。
**需求:** 当参数 id 有值的时候优先使用id查询, 当id没有值时就去判断用户名是否有值, 如果有值就用用户名查询。 id和用户名都没有值,查询无结果。
前提条件 : 在已有的sys_user表中, 除了主键id外, 我们认为user_name(用户名) 也是唯一的。
```
<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>
```
# 4.3 where、 set、 trim用法
4.3 where、 set、 trim用法这3个标签解决了类似的问题, 并且where和set都属于trim的一种具体用法。
4.3.1 where用法
**where标签的作用:**
* 如果该标签包含的元素中有返回值时插入一个where。没有返回值,不出现where。
* 如果where后面的字符串是以AND和OR开头的, 就将它们剔除。
![](https://box.kancloud.cn/e9d19484b05fe34a95e94c7afddf25ff_561x471.png)
4.3.2 set用法
set标签的作用:
* 如果该标签包含的元素中有返回值, 就插入一个set。没有值,不出现set。
* 如果set标签中后面的字符串是以逗号结尾的, 就将这个逗号剔除。
![](https://box.kancloud.cn/3cc6718fdc2b64c73236da177dc0dd4d_561x601.png)
**注意:** set标签解决了sql中逗号问题。但是当set元素中没有任何值时,sql语句变为 update sys_user where id = #{id},不符合sql规范。set标签使用时注意。
## 4.3.3 trim用法
where 和 set 标签的功能都可以用 trim 标签来实现
**where标签对应trim的实现如下**
![](https://box.kancloud.cn/e35f9d61660ce60cf4296696473adb8c_720x123.png)
**set标签对应的trim 实现如下**
![](https://box.kancloud.cn/db99dd3190f3a7d0c5012fdebd1d98c0_622x119.png)
* prefix: 当trim元素内包含内容时, 会给内容增加prefix指定的前缀。
* prefixOverrides: 当trim元素内包含内容时, 会把内容中匹配的前缀字符串去掉。
* suffix: 当trim元素内包含内容时, 会给内容增加suffix指定的后缀。
* suffixOverrides: 当trim元素内包含内容时, 会把内容中匹配的后缀字符串去掉。
# 4.4 foreach用法
## 4.4.1 foreach实现in集合
* foreach标签元素 collection: 必填, 值为要迭代循环的属性名。 这个属性值的情况有很多。
* item: 变量名, 值为从迭代对象中取出的每一个值(用值的属性名)。
* index: 索引的属性名, 在集合数组情况下值为当前索引值, 当迭代循环的对象是Map类型时, 这个值为Makey(键值) 。
* open: 整个循环内容开头的字符串。
* close: 整个循环内容结尾的字符串。
* separator: 每次循环的分隔符。
**
当采用如下方法使用数组参数时, 就需要把foreach标签中的collection属性值设置为array。**
![](https://box.kancloud.cn/692301e9f4c121bd51a407692bfeb997_720x265.png)
**2.有多个参数**
第2章中讲过, 当有多个参数的时候, 要使用@Param注解给每个参数指定一个名字, 否则在SQL中使用参数时就会不方便, 因此将ollection设置为@Param注解指定的名字即可。
**3.参数是Map类型**
使用Map和使用@Param注解方式类似, 将collection指定为对应Map中的key即可。 如果要循环所传入的Map:1. 推荐使用@Param注解指定名字, 此时可将collection设置为指定的名字2.不想指定名字, 就使用默认值_parameter。
**4.参数是一个对象**
这种情况下指定为对象的属性名即可。 当使用对象内多层嵌套的对象时, 使用属性.属性(集合和数组可以使用下标取值) 的方式可以指定深层的属性值。
## 4.4.2 foreach实现批量插入
批量插入:将多条数据一次插入到表中。
**批量插入的sql语法**
![](https://box.kancloud.cn/77ba4a4b0cf13ab3b00c90e1e9e61252_635x120.png)
![](https://box.kancloud.cn/127a4f498dd8b64d7f8f2a33f665232a_554x273.png)
![](https://box.kancloud.cn/6ac2b21c529125450d44537f58e984db_647x388.png)
![](https://box.kancloud.cn/7f1cc8170e1b060db74fdad073894589_619x346.png)
**MyBatis 3.3.1以后,MySQL支持批量插入回写主键值,实现:**
需要在xml中新增两个属性useGeneratedKeys和keyProperty<insert id="insertList" useGeneratedKeys="true" keyProperty="id">
![](https://box.kancloud.cn/d84fa7f8975de70114093e4f88f1bd55_561x256.png)
## 4.4.3 foreach实现动态UPDATE
这节不是讲循环更新多条数据,是讲传入的参数是map时,更新一条数据的多个值。
![](https://box.kancloud.cn/e0bcba64b46521848339e254f0e28498_623x239.png)
这里没有通过@Param 注解指定参数名, 因而 MyBatis 在内部的上下文中使用了默认值_parameter作为该参数的key, 所以在XML中也使用了_parameter。
![](https://box.kancloud.cn/f92a7bae9e5f740b2a23e76bad904055_491x444.png)
**日志**
![](https://box.kancloud.cn/869b39305e85d4db0568c99fb553fc68_495x365.png)
# 4.5 bind用法
![](https://box.kancloud.cn/fcae289ea142e809e439be47f9cbeae6_588x103.png)
使用concat函数连接字符串, 在MySQL中, 这个函数支持多个参数, 但在Oracle中只支持两个参数。 由于不同数据库之间的语法差异。 如果更换数据库, 有些SQL语句可能就需要重写。 针对这种情况, 可以使用 bind 标签来避免由于更换数据库带来的一些麻烦。 将上面的方法改为bind方式后, 代码如下。
!![](https://box.kancloud.cn/aef12bf1bc45b14673a6aa3a9f7fa334_620x122.png)
bind标签的两个属性都是必选项, name为绑定到上下文的变量名,value为OGNL表达式。 创建一个bind标签的变量后, 就可以在下面直接使用, 使用bind拼接字符串不仅可以避免因更换数据库而修改 SQL, 也能预防 SQL 注入
# 4.6 多数据库支持
对不同的数据库,使用不同的sql支持。MyBatis可以根据不同的数据库厂商执行不同的语句, 这种多厂商的支持是基于映射SQL语句中的 databaseId 属性的。 MyBatis 会加载不带databaseId 属性和带有匹配当前数据库databaseId属性的所有语句。 如果同时找到带有 databaseId和不带 databaseId的相同语句, 选择使用带databaseId的语句。
**步骤:**
**1. **mybatis-config.xml文件中加入databaseIdProvider配置即可。<databaseIdProvider type="DB_VENDOR"/> DB_VENDOR是数据库产品名, 数据库产品名一般由所选择的当前数据库的 JDBC 驱动所决定, 通过调用DatabaseMetaData接口实现类的getDatabaseProductName()方法得到。
**2.** 在有property配置时,databaseId将被设置为第一个能匹配数据库产品名称的属性键对应的值, 如果没有匹配的属性则会被设置为 null。 数据库产品名字符串都非常长而且相同产品的不同版本会返回不同的值, 所以通常会通过设置属性别名NAME 来使其变短。
![](https://box.kancloud.cn/e59231ca0f3a2d63a839c4716478389d_626x304.png)
当DB_VENDOR的数据库产品名字符串中包含了property的name的值, databaseId就会被设置为value的值。
**3**. 在相关xml中编写对于不同数据库的sql语句
![](https://box.kancloud.cn/4c1e9e0fc3bf7258c5c57a22e4d1e6e7_423x483.png)
# 4.7 OGNL用法
e.method(args) : 调用对象方法
e.property: 对象属性值
e1[e2]: 按索引取值(List、 数组和Map)
@class@method(args) : 调用类的静态方法
@class@field: 调用类的静态字段值!
![](https://box.kancloud.cn/077d3a42d73393455f304d9d15fdcca6_663x136.png)
假设User类型的属性user中有一个Address类型的属性名为addr, 在Address中还有一个属性zipcode, 可以通过user.addr.zipcode直接使用zipcode的值。map.userName来获取map中key为userName的值, 这里一定要注意, 不管userName的值是不是null, 必须保证userName这个key存在, 否则就会报错。调用类中的静态方法!
![](https://box.kancloud.cn/8ad94fab1f76806fe36da69c93d17c73_748x130.png)
![](https://box.kancloud.cn/e5746cabf56614dfdeabe1cdfad166c0_719x282.png)
** 总结:**
动态 SQL 可以避免在Java代码中处理繁琐的业务逻辑, 通过将大量的判断写入到MyBatis的映射层可以极大程度上提高我们的逻辑应变能力。当有一般的业务逻辑改动时, 通常只需要在映射层通过动态SQL即可实现。