目錄:
- 動態SQL中的 “元素” :
- \<if>元素
- \<choose>、\<when>、\<otherwise>元素
- \<where>、\<trim>元素
- \<set>元素
- \<foreach>元素
- \<bind>元素
作者簡介 :一只大皮卡丘,計算機專業學生,正在努力學習、努力敲代碼中! 讓我們一起繼續努力學習!
該文章參考學習教材為:
《Java EE企業級應用開發教程 (Spring + Spring MVC +MyBatis)》 黑馬程序員 / 編著
文章以課本知識點 + 代碼為主線,結合自己看書學習過程中的理解和感悟 ,最終成就了該文章文章用于本人學習使用 , 同時希望能幫助大家。
歡迎大家點贊👍 收藏? 關注💖哦!!!(侵權可聯系我,進行刪除,如果雷同,純屬巧合)
- 在使用 JDBC 或其他類似的框架進行數據庫開發時,通常都要根據需求去手動拼裝SQL,這是一個非常麻煩且痛苦的工作,而 MyBatis提供的對SQL語句動態組裝的功能,能很好地解決這一麻煩工作。
動態SQL中的 “元素” :
動態SQL 是MyBatis的強大特性之一,MyBatis3 采用了功能強大的基于 OGNL的表達式 來 完成動態SQL,
它消除了之前版本中需要了解的大多數元素,使用不到原來一半的元素就能完成所需工作。MyBatis動態SQL 中的 主要元素 (用在 “映射文件” 中),如下表所示 :
元素 說明 <if> 判斷語句,用于單條件分支判斷。 <choose> ( <when>、<otherwise> ) 相當于Java中的switch…case…default語句,用于多條件分支判斷。 <where>、<trim>、 <set> 輔助元素,用于處理一些SQL拼裝、特殊字符問題。 <foreach> 循環語句,常用于 in語句 等列舉條件中 <bind> 從OGNL表達式中創建一個變量,并將其綁定到上下文,常用于模糊查詢的sql中。
<if>元素
在MyBatis中,<if>元素是最常用的判斷語句,它類似于Java中的 if語句,主要用于實現某些簡單的條件選擇。在實際開發中, 我們可能會通過多個條件來精確地查詢某個數據。<if>元素要結合其的 test屬性一起使用。使用 <if>元素時,只要test屬性中的表達式為true,就會執行元素中的條件語句。
例如,要查找某個客戶的信息, 可以通過姓名和職業來查找客戶,也可以不填寫職業直接通過姓名來查找客戶,還可以都不填寫而查詢出所有客戶,此時姓名和職業就是非必須條件 ( 可能通過這兩個條件來查詢,也可能取其中之一來進行查詢,如果此時還要程序員通過代碼來判斷就會顯得很麻煩,可通過動態SQL的 if元素 來根據實際情況來進行 “查詢”)。類似于這種情況,在 MyBatis 中就可以通過 <if>元素來實現。
<if>元素進行 “動態SQL” 操作例子如 :
Customer.java//使用注解來為該POJO類設置在 mybatis-config.xml配置文件中使用的 "別名" @Alias(value = "customer") // Alias : 設置別名 public class Customer { // "顧客"類 --"持久化"類private Integer id; //主鍵private String username; //客戶名稱private String jobs; //職業private String phone; //電話public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getJobs() {return jobs;}public void setJobs(String jobs) {this.jobs = jobs;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}@Overridepublic String toString() {return "Customer{" +"id=" + id +", username='" + username + '\'' +", jobs='" + jobs + '\'' +", phone='" + phone + '\'' +'}';} }
mybatis-config.xml :(配置文件)
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><!--Mybatis的配置文件: mybatis-config.xml : 為全局配置文件,配置了“運行環境的信息”,主要內容是 : 數據庫的連接 (其屬于Mybatis操作步驟的第一步 : 讀取Mybatis-config.xml配置文件的"先行要準備好的內容")--><!-- 1.配置環境,默認環境id為mysql --><environments default="mysql"><environment id="mysql"><transactionManager type="JDBC"/><!-- 用于指定MyBatis獲取數據庫連接的方式。“POOLED”代表的是連接池。 --><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><!-- 2.配置Mapper文件的位置 : mybatis.config.xml配置文件要讀取映射文件: 即讀取mapper.xml文件 --><!-- 因為按照"Mybatis框架的執行流程圖 : 加載了mybatis-config.xml配置文件之后,是要加載"映射文件"的,所以 --><!-- 讀取"映射文件" --><mappers><mapper resource="CustomerMapper.xml"/></mappers></configuration>
CustomerMapper.xml :(映射文件)
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- namespace的命名空間 --> <mapper namespace="CustomerMapper"><!-- 使用resultMap解決 “屬性名” 和 “字段名”不一致的,使得數據能成功映射到POJO類中 --><resultMap id="resultMap_Customer" type="Customer"><id property="id" column="t_id"/><result property="username" column="t_username"/><result property="jobs" column="t_jobs"/><result property="phone" column="t_phone"/></resultMap><!-- 動態SQL,解決實際開發中“查詢”條件不確定,讓程序自行判斷是否要將其加入到“查詢SQL語句”中的情況 --><!-- if元素使用 --><!-- test="username != null and username != '' 對username屬性進行 “非空判斷” --><select id="findCustomerByNameAndJob" parameterType="com.myh.po.Customer" resultMap="resultMap_Customer">select * from t_customer where 1=1<if test="username != null and username != ''">and t_username like concat('%',#{username},'%')</if><if test="jobs != null and jobs !=''">and t_jobs = #{jobs}</if></select></mapper>
動態SQLTest.java : (測試類 )
public class 動態SQLTest {/*** 根據客戶姓名和職業組合條件查詢客戶信息列表*/@Test //單元測試public void findCustomerByNameAndJobsTest() throws IOException {//1.讀取mybatis-config.xml配置文件 (通過"輸入流"來讀取)String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//2.根據配置文件(通過SqlSessionFactoryBuilder對象 )創建"會話工廠"對象 : SqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//3.獲得SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();//創建Customer對象,組合封裝組合查詢的條件Customer customer = new Customer();customer.setUsername("小明");//執行SqlSession的selectList()方法List<Customer> customers = sqlSession.selectList("CustomerMapper.findCustomerByNameAndJob", customer);//輸出查詢結果for (Customer customer1 : customers) {System.out.println(customer1);}//管邊SqlSessionsqlSession.close();} }
控制臺運行結果 :
從上圖可以看出,傳遞的只有一個參數 username=‘小明’,另一個屬性沒傳遞參數,此時”映射文件“中進行了動態SQL來動態的根據參數來查詢數據庫中的數據。
<choose>、<when>、<otherwise>元素
在使用 <if>元素時,只要test屬性中的表達式為true,就會執行元素中的條件語句,但是在實際應用中,有時只需要從多個選項中選擇一個去執行。
例如下面的場景:
當客戶名稱不為空,則只根據客戶名稱進行客戶篩選;
當客戶名稱為空,而客戶職業不為空,則只根據客戶職業進行客戶篩選。
當客戶名稱和客戶職業都為空,則要求查詢出所有電話不為空的客戶信息。”
此種情況下,使用 <if>元素進行處理是非常不合適的。如果使用的是Java語言,這種情況顯然更適合使用 switch…case…default語句來處理。針對以上這種情況 / 這種開發需求,MyBatis 中可以使用 <choose>、<when>、 <otherwise> 元素進行處理。例子如 :
CustomerMapper.xml :
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- namespace的命名空間 --> <mapper namespace="CustomerMapper"><!-- 使用resultMap解決 “屬性名” 和 “字段名”不一致的,使得數據能成功映射到POJO類中 --><resultMap id="resultMap_Customer" type="com.myh.po.Customer"><id property="id" column="t_id"/><result property="username" column="t_username"/><result property="jobs" column="t_jobs"/><result property="phone" column="t_phone"/></resultMap><!-- <choose> <when> <otherwise> 元素的使用 (動態SQL) --><!-- <choose> <when> <otherwise> 分別代表 Java中的 switch case default (功能也是相同的,前面的獲取了,后面的就不再獲取了) --><select id="findCustomerByNameAndJob2" parameterType="com.myh.po.Customer" resultMap="resultMap_Customer">select * from t_customer where 1=1<choose><when test="username != null and username != ''">and t_username like concat('%',#{username},'%')</when><when test="jobs != null and jobs != ''">and t_jobs = #{jobs}</when><otherwise>and t_phone is not null</otherwise></choose></select> </mapper>
在上述代碼中,使用了 <choose>元素進行SQL拼接,當第一個 <when>元素中的條件為真,則只動態組裝第一個<when>元素 內的SQL片段,否則就繼續向下 判斷第二個<when>元素中的條件是否為真,以此類推。當前面所有 <when>元素中的條件都不為真時,則只組裝<otherwise>元素內的SQL片段。
動態SQLTest.java
public class 動態SQLTest {/*** 根據客戶姓名、職業和電話號碼進行類型于 switch、case、default式開發*/@Test //單元測試public void findCustomerByNameAndJobsTest2() throws IOException {//1.讀取mybatis-config.xml配置文件 (通過"輸入流"來讀取)String resource = "com/myh/動態SQL/mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//2.根據配置文件(通過SqlSessionFactoryBuilder對象 )創建"會話工廠"對象 : SqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//3.獲得SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();//創建Customer對象,組合封裝組合查詢的條件Customer customer = new Customer();customer.setJobs("學生");//執行SqlSession的selectList()方法List<Customer> customers = sqlSession.selectList("CustomerMapper.findCustomerByNameAndJob2", customer);//輸出查詢結果for (Customer customer1 : customers) {System.out.println(customer1);}//管邊SqlSessionsqlSession.close();} }
控制臺輸出結果1 :
上述的輸出結果是 :只有一個jobs的參數 : 學生。所以按照switch 、case、default,被“映射文件”中的第二個<when>元素攔截,所以有以上結果。
控制臺輸出結果2 :
將 customer.setJobs(“學生”);語句注釋掉,則會按照第三個條件 :查詢出所有電話不為空的客戶信息。相當于用default / <otherwise> 元素中的sql語句來查詢數據庫。 輸出內容如下所示 :
<where>、<trim>元素
在前兩個元素的案例中,映射文件中編寫的SQL后面都加入了"where 1=1”的條件,那么到底為什么要這么寫呢?如果將where 后“1=1” 的條件去掉,那么MyBatis所拼接出來的SQL將會如下所示 :
select * from t_customer where 1=1 and t_username like concat('%',?,'%')
上面SQL中,where后直接跟的是and,這在運行時肯定會報SQL語法錯誤 ( 開發中,如果沒有where 1=1 ,直接用 if 和 choose元素 的話是會報錯的 ),而加入了條件“1=1”后,既保證了where后面的條件成立,又避免了where后面第一個詞是 and或者or之類的關鍵詞。那么在MyBatis 中,有沒有什么辦法不用加入“1=1”這樣的條件,也能使拼接后的SQL成立 呢?
-----MyBatis 提供了 <where>元素來處理這樣的問題。 ( 將 where 1 = 1刪除 , 用<where>元素進行替換)
<!-- 用<where>元素來替代 where 1=1 --><select id="findCustomerByNameAndJob3" parameterType="com.myh.po.Customer" resultMap="resultMap_Customer">select * from t_customer<where><if test="username != null and username != ''">and t_username like concat('%',#{username},'%')</if><if test="jobs != null and jobs !=''">and t_jobs = #{jobs}</if></where></select>
上述配置代碼中,使用 <where>元素對“where 1=1” 條件 進行了替換,<where>元素 會自動判斷 組合條件下拼裝的SQL語句, 只有 <where>元素內的條件成立時,才會在拼接SQL中加入where關鍵字,否則將不會添加 ; 即使where之后的內容 有多余的“AND” 或 “OR”,<where>元素也會自動將它們去除。
除了使用 <where>元素外,還可以通過 <trim>元素來定制需要的功能,上述代碼還可以修改為如下形式
<!-- <trim>元素 --><!-- prefix : 表示語句的前綴, prefix="where" : 用where來進行sql語句拼接 --><!-- prefixOverrides : 表示“要去除的特殊字符”, prefixOverrides="and" : 去除sql語句中的and --><select id="findCustomerByNameAndJob4" parameterType="com.myh.po.Customer" resultMap="resultMap_Customer">select * from t_customer<trim prefix="where" prefixOverrides="and"><if test="username != null and username != ''">and t_username like concat('%',#{username},'%')</if><if test="jobs != null and jobs !=''">and t_jobs = #{jobs}</if></trim></select>
上述配置代碼中,同樣使用 <trm>元素對 "where1=1” 條件進行了替換,<trim>元素的作用是去除一些特殊的字符串, 它的 prefix屬性代表的是語句的前綴 ( 這里使用where來連接后面的SQL片段 ) ,而 prefixoOverrides屬性代表的是需要去除的那些特殊字符串 ( 這里定義了要去除SQL中的and ),上面的寫法和使用 <where>元素基本是等效的。
<set>元素
在Hibernate中,如果想要更新某一個對象, 就需要發送所有的字段給持久化對象,然而實際應用中,大多數情況下都是更新的某一個或幾個字段。 如果更新的每一條數據都要將其所有的屬性都更新一遍,那么其執行效率是非常差的
( 而且實際開發中要修的內容是不定的,如果要預估所有的情況而提前設置好合適的set語句來修改數據庫,顯然是不合理的,這時可用動態SQL來解決,傳來什么數據則設置修改什么數據 )。為了解決上述情況中的問題,MyBatis中提供了 <set>元素來完成這一工作。<set>元素主要用于更新操作,其主要作用是在動態包含的SQL語句前輸出一個SET關鍵字,并將SQL語句中最后一個多余的逗號去除。 (根據傳入的“參數”的情況來動態的修改 數據庫中的“數據”,通過<set>元素中的<if>元素來動態判斷)
<!-- <set>元素 : 動態修改操作 --><!-- <set>元素會動態判斷去修改數據庫中的數據,同時會將多余的"逗號"去除掉 --><update id="updateCustomer" parameterType="com.myh.po.Customer">update t_customer<set><if test="username != null and username != ''">set t_username = #{username},</if><if test="jobs != null and jobs != ''">set t_jobs=#{jobs},</if><if test="phone != null and phone != ''">set t_phone=#{phone},</if></set>where id = #{id}</update>
注意 :
在映射文件中使用 <set>和 <if>元素組合進行update語句動態SQL組裝時,如果 <set> 元素內包含的內容都為空,則會出現SQL語法錯誤。所以在使用 <set>元素進行字段信息更新時,要確保傳入的更新字段不能都為空。
<foreach>元素
在實際開發中,有時可能會遇到這樣的情況 : 假設在一個客戶表中有 1000條數據,現在需要將id值小于 100 的客戶信息全部查詢出來,這要怎么做呢? 有人也許會說,“我可以一條一條查出來”,那如果查詢200、300甚至更多也一條條查嗎? 這顯然是不可取的。
有的人會想到,可以在Java方法中使用循環,將查詢方法放在循環語句中,然后通過條件循環的方式查詢出所需的數據。這種查詢方式雖然可行,但每執行一次循環語句, 都需要向數據庫中發送一條查詢SQL,其查詢效率是非常低的。此時我們可以通過SQL語句來執行這種查詢。MyBatis中已經提供了一種用于數組 和 集合循環遍歷的方式,那就是使用 <foreach>元素,我們完全可以通過 <foreach>元素來解決上述類似的問題。
<foreach>元素通常在構建IN條件語句時使用,其使用方式如下 :
<!-- <foreach>元素使用 --><!-- <foreach>元素通常在構建IN條件語句中使用 --> <select id="findCustomerByIds" parameterType="List" resultMap="resultMap_Customer">select * from t_customer where id in <foreach item="id" index="index" collection="list" separator="," open="(" close=")">#{id}</foreach> </select>
假設上述 傳入的id集合中內容為 : 1,3,5 。那么上述代碼中 最后拼接的sql 語句 為 : select * from t_customer where id in (1,3,5) ( foreache元素中的各個屬性的作用將在下面中講述。)
( 通過以上代碼可實現 <foreach> 元素對傳入的客戶編號集合進行了動態SQL組裝,最終成功批量查詢出了對應的客戶信息。)在上述“映射文件”代碼中,使用了 <foreach>元素對傳入的集合進行遍歷并進行了動態SQL組裝。關于 <foreach>元素中使用的幾種屬性的描述具體如下:
屬性 描述 item 配置 循環中當前的 元素。
如 : 傳入一個1,3,5的id集合,item分別代表其中的1,3,5。index 配置 當前元素在集合的位置下標。 collection 配置 傳遞過來的 “參數類型” ( 首字母小寫 )。它可以是一個array、list ( 或 collection)、Map 集合的鍵、POJO包裝類中數組或 集合類型的屬性名等。 open “遍歷所有元素” 之前要 “插入 / 配置” 的 “前綴”。 close “遍歷所有元素” 之后要 “插入 / 配置” 的 “后綴”。 open 和 close 配置的是 以什么符號將這些集合元素包裝起來。 separator 配置的是 各個元素的間隔符。 注意一:
可迭代對象** (如列表、集合等) 和任何的字典或者數組對象傳遞給 <foreach>作為集合參數。當使用 可迭代對象或者數組時,index 是當前迭代的次數,item 的值是本次迭代獲取的元素。
當使用字典(或者Map.Entry對象的集合)時,index 是鍵,item 是值。
注意二:
在使用 <foreach> 時最關鍵也是最容易出錯的就是collection 屬性,該屬性是必須指定的,而且在不同情況下,該屬性的值是不一樣的。主要有以下3種情況。
(1) 如果傳入的是 單參數且參數類型是一個數組或者List 的時候,collection 屬性值分別為
array和 list ( 或collection )。
(2) 如果傳入的參數是多個的時候,就需要把它們封裝成一個Map了,當然單參數也可以
封裝成Map集合,這時候==collection屬性值就為Map的鍵。
(3) 如果傳入的參數是POJO包裝類的時候,collection屬性值就為該包裝類中需要進行遍歷
的數組或集合的 屬性名。
設置collction 屬性值的時候,必須按照實際情況配置,否則程序就會出現異常。
Test.java (測試類):
/*** 根據客戶端“編號”查詢客戶信息*/@Test //單元測試public void findCustomerById() throws IOException {//1.讀取mybatis-config.xml配置文件 (通過"輸入流"來讀取)String resource = "com/myh/動態SQL/mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//2.根據配置文件(通過SqlSessionFactoryBuilder對象 )創建"會話工廠"對象 : SqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//3.獲得SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();//創建List集合,封裝查詢idList<Integer> ids = new ArrayList<Integer>();ids.add(1);ids.add(2);//執行SqlSession的查詢方法List<Customer> customers = sqlSession.selectList("CustomerMapper3.findCustomerByIds", ids);//傳入參數是List類型,所以collection="list"//打印輸出結果for (Customer customer = customers ) {System.out.println(customer);}//關閉SqlSession}
上面傳的 參數是類型是List類型,所以 “映射文件”中的collection的值為: list。同時集合中的id數據為 : 1,2。 “映射文件” 會根據傳入的1,2來進行數據庫查詢。
<bind>元素
在進行 模糊查詢 編寫SQL語句的時候,如果使用 “${}" 進行 字符串拼接,則無法防上sql注入問題;
如果使用concat函數進行拼接,則只針對MySQL數據庫有效;如果使用的是Oracle數據庫,則要使用連接符號“ ||”。 這樣,映射文件中的SQL就要根據不同的情況提供不同形式的實現,這顯然是比較麻煩的,且不利于項目的移植。為此,MyBatis 提供了 <bind>元素來解決這一問題,我們完全不必使用數據庫語言,只要使用MyBatis的語言即可與所需參數連接。MyBatis的 <bind>元素 可以通過OGNL表達式來創建一個上下文變量, 其使用方式如下 :
“映射文件” 中代碼 :
<!-- <bind>元素的使用: 根據客戶名模糊查詢"客戶信息" --><select id="findCustomerByName" parameterType="List" resultMap="resultMap_Customer">-- _parameter.getUsername()也可直接寫入傳入的字段屬性名,即username<bind name="pattern_username" value="'%'+_parameter.getUsername()+'%'"/>select * from t_customer where username like #{pattern_username}</select>
上述配置代碼中,使用 <bind>元素定義了一個name為 pattern_username 的變量,<bind>
元素中value的屬性值就是拼接的查詢字符串,其中 _parameter.getUsername( )表示傳遞進來的
參數 (也可以直接寫成對應的參數變量名,如username )。在SQL語句中,直接引用 <bind>元
素的name屬性值即可進行動態SQL組裝。
單元測試 :
/*** <bind>元素的使用 : 根據客戶名模糊查詢客戶信息 (使用<bind>元素進行動態SQL組裝)*/@Test //單元測試public void findCustomerByNameTest() throws IOException {//1.讀取mybatis-config.xml配置文件 (通過"輸入流"來讀取)String resource = "com/myh/動態SQL/mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);//2.根據配置文件(通過SqlSessionFactoryBuilder對象 )創建"會話工廠"對象 : SqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//3.獲得SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();Customer customer = new Customer();customer.setUsername("明");//執行SqlSession的查詢方法,返回結果集List<Customer> customers = sqlSession.selectList("CustomerMapper3.findCustomerByName", customer);//輸出結果for (Customer customer1 : customers) {System.out.println(customer1);}}