*****
# 4.1 if用法
**if標簽的作用:**
* 通過判斷參數值來決定是否使用某個查詢條件。?
* 用于UPDATE語句中判斷是否更新某一個字段。
* 在INSERT語句中用來判斷是否插入某個字段的值。
## 4.1.1 在WHERE條件中使用if
例子: 實現一個用戶管理高級查詢功能,支持以下三種情況
1. 只根據用戶名進行模糊查詢。
2. 只根據郵箱進行完全匹配。
3. 根據用戶名和郵箱查詢匹配的用戶。
**不使用動態sql會出現的問題:**

當只提供userName的參數值a時, userEmail默認是null,這時會查userName=a and email=null的用戶,查詢結果不正確。
**使用動態sql**

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開頭的, 就將它們剔除。

4.3.2 set用法
set標簽的作用:
* 如果該標簽包含的元素中有返回值, 就插入一個set。沒有值,不出現set。
* ?如果set標簽中后面的字符串是以逗號結尾的, 就將這個逗號剔除。

**注意:** set標簽解決了sql中逗號問題。但是當set元素中沒有任何值時,sql語句變為 update sys_user?where id = #{id},不符合sql規范。set標簽使用時注意。
## 4.3.3 trim用法
where 和 set 標簽的功能都可以用 trim 標簽來實現
**where標簽對應trim的實現如下**

**set標簽對應的trim 實現如下**

* ?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。**

**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語法**




**MyBatis 3.3.1以后,MySQL支持批量插入回寫主鍵值,實現:**
需要在xml中新增兩個屬性useGeneratedKeys和keyProperty<insert id="insertList" useGeneratedKeys="true" keyProperty="id">

## 4.4.3 foreach實現動態UPDATE
這節不是講循環更新多條數據,是講傳入的參數是map時,更新一條數據的多個值。

這里沒有通過@Param 注解指定參數名, 因而 MyBatis 在內部的上下文中使用了默認值_parameter作為該參數的key, 所以在XML中也使用了_parameter。

**日志**

# 4.5 bind用法

使用concat函數連接字符串, 在MySQL中, 這個函數支持多個參數, 但在Oracle中只支持兩個參數。 由于不同數據庫之間的語法差異。 如果更換數據庫, 有些SQL語句可能就需要重寫。 針對這種情況, 可以使用 bind 標簽來避免由于更換數據庫帶來的一些麻煩。 將上面的方法改為bind方式后, 代碼如下。
!
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 來使其變短。

當DB_VENDOR的數據庫產品名字符串中包含了property的name的值, databaseId就會被設置為value的值。
**3**.? 在相關xml中編寫對于不同數據庫的sql語句

# 4.7 OGNL用法
e.method(args) : 調用對象方法?
e.property: 對象屬性值?
e1[e2]: 按索引取值(List、 數組和Map)
@class@method(args) : 調用類的靜態方法
@class@field: 調用類的靜態字段值!

假設User類型的屬性user中有一個Address類型的屬性名為addr, 在Address中還有一個屬性zipcode, 可以通過user.addr.zipcode直接使用zipcode的值。map.userName來獲取map中key為userName的值, 這里一定要注意, 不管userName的值是不是null, 必須保證userName這個key存在, 否則就會報錯。調用類中的靜態方法!


** 總結:**
動態 SQL 可以避免在Java代碼中處理繁瑣的業務邏輯, 通過將大量的判斷寫入到MyBatis的映射層可以極大程度上提高我們的邏輯應變能力。當有一般的業務邏輯改動時, 通常只需要在映射層通過動態SQL即可實現。