開發人員在使用JDBC框架或者其他類似的框架進行數據庫開發時,通常都要根據需求去手動拼接SQL,這樣非常麻煩,而myBatis提供了對SQL語句動態組裝的功能,恰好解決了這一問題。
一,動態SQL中的元素
動態SQL是MyBatis的強大特性之一,MyBatis 3后采用了基於OGNL的表達式來完成動態SQL,
MyBatis動態SQL中的主要元素,如下:
元素 | 說明 |
<if> | 判斷語句,用於單條件分支判斷 |
<choose>(<when>,<otherwise>) | 相當於Java中的switch..case...default |
<where>,<trim>,<set> | 補助元素,用於處理一些SQL拼接,特殊字符問題 |
<foreach> | 循環語句,常用於in語句等例舉條件中 |
<bind> | 從OGNL表達式中創建一個變量,並將其綁定到上下文中,常用於模糊查詢的sql中 |
二.
1.<if>元素:通常用於通過多個條件來精確查詢某個數據
在MyBatis中,<if>元素是最常用的判斷語句,它類似Java中的if語句,主要用於簡單的條件判斷。
(1)在映射文件中使用<if>元素來編寫根據客戶名和客戶職業組合信息來查詢客戶信息.
<!-- 使用if元素來進行查詢 --> <select id="findCustomerByNameAndJobs" parameterType="com.itheima.po.Customer" resultType="com.itheima.po.Customer"> select * from t_customer where 1=1 <if test="username != null and username !='' "> and username like concat('%',#{username},'%') </if> <if test="jobs != null and jobs !='' "> and jobs=#{jobs} </if> </select>
使用<if>元素的test屬性對username和jobs進行了非空判斷(test屬性常用於條件判斷中,用於判斷真假,大部分時候是用於進行非空判斷),如果傳入的條件非空,就進行sql拼接。
(2)在測試類中,編寫測試方法findCustomerByNameAndJobs:
/** * 根據用戶名和用戶的工作來查詢用戶 */ @Test public void findCustomerByNameAndJobsTest() { //通過工具類來生成SqlSession對象 SqlSession sqlSession=MyBatisUtils.getSession(); //創建Customer對象,封裝需要組合查詢的條件 Customer customer=new Customer(); customer.setJobs("teacher"); customer.setUsername("jack"); //執行SqlSession對象的查詢方法,返回一個結果集 List<Customer> customers=sqlSession.selectList("com.itheima.mapper.CustomerMapper.findCustomerByNameAndJobs", customer); //輸出查詢結果 for(Customer customer2:customers) { System.out.println(customer2); } //關閉sqlSession sqlSession.close(); }
(3)測試結果:
(4)如果將上面代碼中的設值注釋掉,如下,就會將所有的信息都輸出來:
1 /** 2 * 根據用戶名和用戶的工作來查詢用戶 3 */ 4 @Test 5 public void findCustomerByNameAndJobsTest() { 6 //通過工具類來生成SqlSession對象 7 SqlSession sqlSession=MyBatisUtils.getSession(); 8 //創建Customer對象,封裝需要組合查詢的條件 9 Customer customer=new Customer(); 10 //customer.setJobs("teacher"); 11 //customer.setUsername("jack"); 12 13 //執行SqlSession對象的查詢方法,返回一個結果集 14 List<Customer> customers=sqlSession.selectList("com.itheima.mapper.CustomerMapper.findCustomerByNameAndJobs", customer); 15 //輸出查詢結果 16 for(Customer customer2:customers) { 17 System.out.println(customer2); 18 } 19 20 //關閉sqlSession 21 sqlSession.close(); 22 }
2.<choose>,<when>,<otherwise>:從多個選項中選一個去執行
在<if>中,只要test屬性中的條件為真,就去執行元素中的條件語句,但是有時候在實際生活中是需要從多個選項中選擇一個去執行,例如
當客戶名不可空,則只根據客戶名進行客戶篩選,
當客戶名為空,職業不為空時就根據客戶職業進行客戶篩選,
如果兩者都為空,就查詢出所有電話不為空的客戶信息
在此種情況下,是不能使用<if>元素的,針對如上情況,可以使用<choose>,<when>,<otherwise>元素組合實現上面的情況
(1).在映射文件中配置如下
<!-- <choose>(<when>,<otherwise>元素的組合使用) --> <select id="findCustomerByNameOrJobs" parameterType="com.itheima.po.Customer" resultType="com.itheima.po.Customer"> select * from t_customer where 1=1 <choose> <when test="username != null and username != '' "> and username like concat('%',#{username},'%') </when> <when test="jobs != null and jobs !='' "> and jobs=#{jobs} </when> <otherwise> and phone is not null </otherwise> </choose> </select>
在上述代碼中,使用了<choose>,<when>,<otherwise>組合,當第一個<when>元素中的條件為真時,則只動態地組裝第一個<when>元素中的條件,否則就繼續向下判斷第二個<when>元素中的條件是否為真,依次類推,如果<when>中的條件都不為真時,就會執行<otherwise>內的SQL片段。
(2)創建測試方法
/** * 根據用戶名或者職業來查詢用戶信息 */ @Test public void findCustomerByNameOrJobs() { //通過工具類來生成SqlSession對象 SqlSession sqlSession=MyBatisUtils.getSession(); //創建Customer對象,封裝需要組合查詢的條件 Customer customer=new Customer(); customer.setJobs("teacher"); customer.setUsername("jack"); //執行SqlSession對象的查詢方法,返回一個結果集 List<Customer> customers=sqlSession.selectList("com.itheima.mapper.CustomerMapper.findCustomerByNameOrJobs", customer); //輸出查詢結果 for(Customer customer2:customers) { System.out.println(customer2); } //關閉sqlSession sqlSession.close(); }
(3)測試結果
(4)當把customer.setUsername("jack")注釋掉后,結果為:
(5)如果把customer.setJobs(“teacher”)也注釋掉后,結果為:
3.<where>,<trim>元素
在前面的小節中編寫的SQL后面都添加了“where 1=1 "這個條件,那么為什么要這么寫呢?是應為如果將后面的”where 1=1“去掉,那么MyBatis拼接出來的SQL語句將會出錯
SELECT *from t_customer where and usename like concat( '%',#{username}, '%');
在上面的語句中,where后面跟的是and,這樣將出錯,而加入了where 1=1 后,既保證了where后面的條件成立,也保證了避免where后面跟着是and或者or.
那么在Mybatis中有什么辦法是可以不加入”1=1“這個條件也可以呢?
MyBatis提供了<where>元素來處理這樣的問題。例如
<select id="findCustomerByNameAndJobs" parameterType="com.itheima.po.Customer"
resultType="com.itheima.po.Customer">
select * from t_customer
<where>
<if test="username != null and username !='' ">
and username like concat('%',#{username},'%')
</if>
<if test="jobs != null and jobs !='' ">
and jobs=#{jobs}
</if>
</where>
</select>
在上面的語句中,使用<where>元素取代了”where 1=1“, where元素會自動判斷組合條件下的拼接的sql語句,只有當<where>里的條件成立時,才會在拼接sql時,加入where元素,否則不會添加,即使where后面有多余的"and",“or”,<where>元素會自動將它們除去。
4.<set>元素
在Hibernate中,如果要更新一條數據,就需要發送所有的字段給持久化對象,然而在實際應用中,大多數情況下,是只跟新某一個字段或者幾個字段,如果更新的每一條數據都要將所有的屬性都更新一遍,那么其執行效率是非常的低,有沒有辦法使程序只更新需要更新的字段?
在MyBatis中,提供了<set>元素來完成這一工作,<set>元素的作用用於更新操作,其主要作用是在動態包含的SQL語句前輸出一個SET關鍵字,並且將SQL語句中最后一個多余的逗號去除。
例如,在入門案例中,使用<set>元素對更新操作的代碼進行修改如下:
<!-- set元素的使用 --> <update id="updateCustomer" parameterType="com.itheima.po.Customer"> update t_customer <set> <if test="username != null and username !='' "> username=#{username}, </if> <if test="jobs != null and jobs != ''"> jobs=#{jobs}, </if> <if test ="phone != null and phone != ''"> phone=#{phone}, </if> </set> where id=#{id} </update>
在上面的sql語句中,使用了<set>和<if>語句相組合的方法將組裝update語句,其中<set>元素會自動的動態前置SET關鍵字,同時也會消除sql語句的最后一個多余的逗號,<if>元素是用來判斷相應的字段是否傳入值,如果傳入的更新字段非空,就將此字段進行動態的組裝,並且更新此字段,否則此字段不更新。
在測試類中編寫如下代碼:
/** *使用set 更新客戶 */ @Test public void updateCustomer1Test() { //1.通過工具類來生成SqlSession對象 SqlSession sqlSession=MyBatisUtils.getSession(); //2.創建Customer對象,並向對象中添加數據 Customer customer=new Customer(); customer.setId(3); customer.setPhone("444444"); //3.執行sqlSession的更新方法,返回的是受影響的行數 int rows=sqlSession.update("com.itheima.mapper.CustomerMapper.updateCustomer", customer); //4.根據返回的結果來判斷是否更新成功 if(rows>0) { System.out.println("更新成功!"); }else { System.out.println("更新失敗!"); } //5.提交事務 sqlSession.commit(); //6.關閉事務 sqlSession.close(); }
測試結果如下:
注意:使用<set>和<if>元素組合進行update語句動態的SQL組裝時,如果<set>中的內容都為空,則會出現sql語法錯誤,所以在<set>元素進行動態更新時,要確保傳入的更新字段不為空。
5.<foreach>元素
該元素是用於數組和集合循環遍歷。
<foreach>元素是通常在構建IN條件語句時使用的,其使用方法如下
<select id="findCustomerByIds" parameterType="List" resultType="com.itheima.po.Customer"> select * from t_customer where id in <foreach item="id" index="index" collection="list" open="(" separator="," close=")"> #{id} </foreach> </select>
在上述代碼中,使用了<foreach>元素對傳入的集合進行了遍歷並進行了動態的SQL組轉,其屬性如下:
item:配置的是循環中當前的元素。
index:配置的是當前元素在集合的位置下標。
collection:配置的list是傳遞過來的參數類型(首字母小寫),她可以是一個array,list(或者是collection),Map集合的建,POJO包裝類中數組或者集合類型的屬性名。
open和close:配置的是以什么符號將這些集合元素包裝起來。
separate:配置的是各個元素之間的間隔符。
在測試類中編寫如下代碼,驗證
/** * 根據客戶編號批量查詢客戶信息 */ @Test public void findCustomerByIdsTest() { //1.獲取SqlSession對象 SqlSession sqlSession=MyBatisUtils.getSession(); //2.創建一個list集合,用來封裝查詢的id值 List<Integer> ids=new ArrayList<Integer>(); ids.add(2); ids.add(3); //3.執行SqlSession的查詢方法,返回結果集 List<Customer> customers=sqlSession.selectList("com.itheima.mapper.CustomerMapper.findCustomerByIds", ids); //4.輸出查詢的結果 for(Customer customer:customers) { System.out.println(customer); } //5.關閉sqlSession sqlSession.close(); }
測試結果如下:
注意:在使用<foreach>元素時,應注意的事項是collection屬性值,該屬性是必須要指定的,並且在不同的情況下,該屬性的值是不同的,主要三種情況:
(1)如果在調用該sql語句時,傳入的是一個參數,並且參數的類型是一個數組或者是list的時候,該collection屬性值就是array和list(或者collection)
(2)如果傳入的是多個參數的時候,就需要把它們封裝成一個Map,單個參數也是可以這樣封裝的,這時候collection屬性值就是Map的鍵
(3)如果傳進來的是POJO的包裝類的時候,collection屬性值就是該包裝類中需要進行遍歷的數組或者集合的屬性名。