經過前面的MyBatis部分學習,已經可使用MyBatis獨立構建一個數據庫程序,基本的增刪查改/關聯查詢等等均可以實現了。簡單的單表操做和關聯查詢在實際開的業務流程中必定會有,可是可能只會佔一部分,不少業務需求每每夾雜着一些須要咱們在後臺去判斷的參數,舉個例子,咱們基本都上過購物網站,想要查看心儀的商品列表,能夠經過商品分類篩選,也能夠經過商品價格來篩選,還能夠同時根據分類和價格來篩選,這裏咱們能夠簡單的理解爲經過商品分類和商品價格的篩選分別爲select語句中where後面的2個子句,相似category=XXX和price > xxx and price <xxx,具體怎麼篩選要看用戶怎麼操做。若是按照以前的路子,咱們要分別定義三個select方法和sql語句,這個就涉及到了一個靜態動態的問題,用戶的操做、輸入等等都是不肯定的,即動態的,可是咱們以前在sql映射文件中寫的語句都是針對單個操做單個想法去寫死的,即靜態的。這樣一來,隨着需求和判斷的的不斷疊加,這個代碼量會很可怕。另一個問題,若是你們有使用Java代碼拼接過複雜SQL語句經歷,應該不會感到很方便,本人使用hibernate的時候也拼接過HQL,共同點就是那些分隔符、空格之類的寫起來很麻煩,也容易出錯。MyBatis提供了動態SQL這一特性,能同時改良上述兩種開發場景。java
MyBatis提供的動態SQL元素實際就是經過在咱們的sql語句中嵌入標籤實現動態,具體標籤以下圖所示。mysql
熟悉jsp、jstl、el表達式那套的,應該對裏面大部分標籤名都不陌生,也比較容易理解,具體用法下面分別進行解析。爲告終合具體的使用場景,將上面元素細分爲四組來演示,分別爲【if、where、trim】、【if、set、trim】、【choose、when、otherwise】、【foreach】sql
private Integer id; //主鍵 private String name; //姓名 private String gender; //性別 private Integer age; //年齡 private String ifIT; //是否從事IT行業
以上爲咱們定義的一我的的屬性,數據庫中也有一我的的數據表。如今假設須要查詢人中的全部男性,同時若是輸入參數中年齡不爲空,就根據性別和年齡查詢。在沒有使用動態SQL以前,按照咱們的慣有思路,咱們須要在Mapper接口中定義兩個查詢方法,同時分別對應在SQL映射文件中定義兩個<select>語句,以下:數據庫
<select id="selectPerson1" parameterType="psn" resultMap="personResultMap"> select * from person where GENDER = '男' </select> <select id="selectPerson2" parameterType="psn" resultMap="personResultMap"> select * from person where GENDER = '男' and AGE = #{age} </select>
這樣一來,隨着相似的須要愈來愈多,咱們的方法和SQL語句量會增長到不少,而且會發現,其實語句中存在不少重複部分。那麼有沒有辦法能同時應對相似的相關需求,同時減小代碼量呢?動態SQL就提供了相關的功能實現這些需求,例如上述場景,咱們便可只需定義一個方法,對應的SQL語句寫成以下:apache
<select id="selectPerson" parameterType="psn" resultMap="personResultMap"> select * from person where GENDER = '男' <if test="age != null"> and AGE = #{age} </if> </select>
在這項<select>咱們將肯定的(靜態的的部分)select * from person where GENDER = '男'和後面的<if>部分結合起來,經過動態SQL提供的<if>標籤給語句預加一層判斷,test屬性值爲布爾類型,true或者false,當爲true(即真)時,纔會把<if>標籤下的內容添加到語句中響應爲值,這裏的test中即判斷輸入參數中年齡是否爲空,不爲空則添加【and AGE = #{age}】到【select * from person where GENDER = '男'】後面,爲空則不加,這樣就達到了同時知足兩種須要,但只定義了一個方法和一條SQL。session
進一步擴展若是想把where後面的部分都動態化,這裏以性別爲例,查詢時若是參數中有不爲空的性別值,則根據性別查詢,反之則查詢全部,有了前面if的學習,咱們不難寫出以下動態SQL:mybatis
<select id="selectPerson1" parameterType="psn" resultMap="personResultMap"> select * from person where <if test="gender != null"> gender = #{gender} </if> </select>
這時候問題來了,當性別不爲空時,語句是 select * from person where gender = #{gender} ,這樣還能正常查詢出咱們想要的結果,可是若是性別爲空,會發現語句變成了 select * from person where ,這顯然是生成一個錯誤的SQL了,爲了解決相似的問題,動態SQL<where>能幫咱們解決這個問題,咱們能夠將上述語句優化成以下:app
<select id="selectPerson1" parameterType="psn" resultMap="personResultMap"> select * from person <where> <if test="gender != null"> gender = #{gender} </if> </where> </select>
這樣mybatis在這裏會根據<where>標籤中是否有內容來肯定要不要加上where,在這裏使用<where>後,若是年齡爲空,則前面引起錯誤的where也不會出現了。jsp
進一步擴展,若是咱們查詢有多個參數須要判斷,根據性別和年齡參數,有了前面<if>和<where>的瞭解,咱們就能夠寫出以下SQL:maven
<select id="selectPerson2" parameterType="psn" resultMap="personResultMap"> select * from person <where> <if test="gender != null"> gender = #{gender} </if> <if test="age != null"> and age = #{age} </if> <where> </select>
乍一看基本沒什麼毛病了,在這裏【性別空、年齡空】、【性別不空、年齡不空】、【性別不空、年齡空】都沒問題,可是若是是【性別空、年齡不爲空】,按理來講語句變成這樣 select * from person where and age = #{age} ,而後若是你動手嘗試一下,就會發現,並不會,這也體現了<where>一個強大之處,它不只會根據元素中內容是否爲空決定要不要添加where,還會自動過濾掉內容頭部的and或者or,另外空格之類的問題也會智能處理。
MyBatis還提供了一種更靈活的<trim>標籤,在這裏能夠替代<where>,如上面的定義能夠修改爲以下,一樣能夠實現效果:
<select id="selectPerson2" parameterType="psn" resultMap="personResultMap"> select * from person <trim prefix="where" prefixOverrides="and |or " > <if test="gender != null"> and gender = #{gender} </if> <if test="age != null"> and age = #{age} </if> </trim> </select>
<trim>標籤共有四個屬性,分別爲【prefix】、【prefixOverrides】、【suffix】、【suffixOverrides】,prefix表明會給<trim>標籤中內容加上的前綴,固然前提是內容不爲空,prefixOverrides表明前綴過濾,過濾的是內容的前綴,suffix和suffixOverrides則分別對應後綴。例如上面的語句中若是性別和年齡都不爲空,<trim>會在添加where前綴的同時,把第一個<if>中的and去掉,這樣來實現<where>一樣的功能。
前面都是查詢,咱們換個更新試試,這裏更新咱們只想更新部分字段,並且要根據參數是否爲空來肯定是否更新,這裏以姓名和性別爲例。
<update id="updatePerson"> update person set <if test="name != null"> NAME = #{name}, </if> <if test="gender != null"> GENDER = #{gender} </if> </update>
這裏若是【姓別爲空】或者【性別和性別都爲空】,相似以前的where問題一樣也來了,因此MyBatis一樣也提供了<set>標籤來解決這一問題,因此上面的定義能夠優化成以下所示
<update id="updatePerson1"> update person <set>
<if test="name != null"> NAME = #{name}, </if> <if test="gender != null"> GENDER = #{gender}, </if> </set>
</update>
在這裏,<set>會根據標籤中內容的有無來肯定要不要加上set,同時能自動過來內容後綴逗號,可是有一點要注意,不一樣於<where>,當<where>中內容爲空時,咱們能夠查出全部人的信息,可是這裏更新語句中,<set>內容爲空時,語句變成 update person ,仍是會出錯,同時更新操做也不會生效了。
以前介紹到的<trim>靈活之處,在這裏經過修改屬性值,也能用來替代<set>,上面的定義使用<trim>改寫以下所示:
<update id="updatePerson2"> update person <trim prefix="set" suffixOverrides=","> <if test="name != null"> NAME = #{name}, </if> <if test="gender != null"> GENDER = #{gender} </if> </trim> </update>
這裏設置前綴爲set,後綴過濾爲逗號「,」,這樣一來,在if判斷以後會引起報錯的逗號將不會存在,一樣能夠實現相關功能。
前面咱們瞭解到的,<select>查詢語句中where後面,<if>經過判斷各個參數是否爲空來肯定是否添加其子句內容到where後面,是一個累加的關係,可是若是咱們的需求,不是累加,而是多選一,例如姓名(name)、性別(gender)、是否從事It行業(ifIT),具體來說就是,若是優先判斷姓名是否有值,有的話就根據姓名查,沒有的話其次再判斷性別是否有值,有的話就根據性別查,也沒有的話就根據是否從事IT行業來查。這樣一來,按照前面瞭解到的<if><where>彷佛有些頭大,不只有多重判斷,還涉及到一個優先級前後問題,一會兒彷佛很難快速想到一個簡單方便的路子。MyBatis一樣提供了一套<choose>、<when>、<otherwise>來幫咱們解決相似上面的問題。
若是以爲不太好記憶,能夠聯想Java中條件判斷的switch,switch對應這裏的<choose>,case對應<when>,一旦某個case條件知足了會break跳出,同時若是都不知足,最後還有個default能夠對應這裏的<otherwise>,因此最終<when>和<otherwise>中有且只有一個會知足,也就只有一項內容會被添加進去。按照上面的需求,咱們能夠寫出以下動態SQL:
<select id="selectPersonExt" parameterType="psn" resultMap="personResultMap"> select * from person <where> <choose> <when test="name!=null"> NAME = #{name} </when> <when test="gender!=null"> GENDER = #{gender} </when> <otherwise> IF_IT = #{ifIT} </otherwise> </choose> </where> </select>
便可方便的實現該場景。
for循環你們應該都不陌生,這裏的foreach一樣主要用來迭代集合,那麼SQL中哪些地方會用到集合呢,用過in的應該比較熟悉,例以下面select語句:
select * from person where age in(10,20,30,40)
上面語句能夠查詢出年齡爲10或20或30或40的人,這些年齡數據是一個集合,可是經過參數傳入的集合是動態的,咱們不可能預知數值和像這樣寫死,MyBatis提供的<foreach>便可實現該功能。該集合做爲參數傳入,以上場景方法定義和SQL語句能夠寫成以下所示:
List<Person> selectForeachAge(List<Integer> ageList);
<select id="selectForeachAge" resultMap="personResultMap"> select * from person where age in <foreach collection="list" item="age" index="i" open="(" close=")" separator=","> #{age} </foreach> </select>
這裏咱們能夠看到,<foreach>共有6個屬性。
item:表示集合迭代時元素的別名,這裏對應#{age}中的age
index:集合迭代時索引,用於表示集合此時迭代到的位置
open、close、separator:這三個分別表明起始、結束、分隔符,在這裏,咱們用過in語句查詢時應該都知道,使用到集合的查詢語句結構關鍵部分能夠描述以下所示: SELECT column_name(s) FROM table_name WHERE column_name IN (value1,value2,...) 。能夠看到在IN關鍵字的後面,集合內容兩邊分別是左括號、右括號,中間集合的各個元素之間用逗號隔開,正好與這裏的三個屬性分別對應。
collection:這個屬性是必要的,在這裏咱們傳入的是單個參數即一個List,因此屬性值就是list。
上面就是動態SQL的各個元素的基本內容,熟悉以後會讓咱們編寫SQL時更加方便和靈活,下面給出完整代碼示例供參考。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 這裏能夠定義類的別名,在mapper.xml文件中應用會方便不少 --> <typeAliases> <typeAlias alias="psn" type="com.mmm.pojo.Person" /> </typeAliases> <!-- 環境配置 --> <environments default="envir"> <environment id="envir"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.0.100:3306/ssm?characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="abc123"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/mmm/mapper/personMapper.xml"/> </mappers> </configuration>
package com.mmm.pojo; public class Person { private Integer id; //主鍵 private String name; //姓名 private String gender; //性別 private Integer age; //年齡 private String ifIT; //是否從事IT行業 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getIfIT() { return ifIT; } public void setIfIT(String ifIT) { this.ifIT = ifIT; } @Override public String toString() { return "Person [id=" + id + ", name=" + name + ", gender=" + gender + ", age=" + age + ", ifIT=" + ifIT + "]"; } }
package com.mmm.mapper; import java.util.List; import com.mmm.pojo.Person; public interface PersonMapper { //查找全部Person對象,返回集合類型,用於在測試類中查看動態SQL結果 List<Person> selectAll(); //用於測試查詢語句中if、where、trim List<Person> selectPerson(Person p); List<Person> selectPerson1(Person p); List<Person> selectPerson2(Person p); //用於測試更新語句中if、set、trim void updatePerson(Person p); void updatePerson1(Person p); void updatePerson2(Person p); //用於測試choose、where、otherwise List<Person> selectPersonExt(Person p); //用於測試foreach List<Person> selectForeachAge(List<Integer> ageList); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.mmm.mapper.PersonMapper"> <resultMap type="psn" id="personResultMap"> <id column="ID" property="id" /> <result column="NAME" property="name" /> <result column="GENDER" property="gender" /> <result column="AGE" property="age" /> <result column="IF_IT" property="ifIT" /> </resultMap> <select id="selectAll" resultMap="personResultMap"> select * from person </select> <!-- 針對查詢語句中if --> <select id="selectPerson" parameterType="psn" resultMap="personResultMap"> select * from person where GENDER = '男' <if test="age != null"> and AGE = #{age} </if> </select> <!-- 針對where --> <select id="selectPerson1" parameterType="psn" resultMap="personResultMap"> select * from person <where> <if test="gender != null"> gender = #{gender} </if> </where> </select> <!-- 針對where的 trim轉換 --> <select id="selectPerson2" parameterType="psn" resultMap="personResultMap"> select * from person <trim prefix="where" prefixOverrides="and |or " > <if test="gender != null"> and gender = #{gender} </if> <if test="age != null"> and age = #{age} </if> </trim> <trim prefix="" prefixOverrides="" suffix="" suffixOverrides=""></trim> </select> <!-- 針對更新語句中if --> <update id="updatePerson"> update person set <if test="name != null"> NAME = #{name}, </if> <if test="gender != null"> GENDER = #{gender} </if> where ID = #{id} </update> <!-- 針對set --> <update id="updatePerson1"> update person <set> <if test="name != null"> NAME = #{name}, </if> <if test="gender != null"> GENDER = #{gender} </if> </set> </update> <!-- 針對set的trim轉換 --> <update id="updatePerson2"> update person <trim prefix="set" suffixOverrides=","> <if test="name != null"> NAME = #{name}, </if> <if test="gender != null"> GENDER = #{gender} </if> </trim> </update> <!-- choose when otherwise --> <select id="selectPersonExt" parameterType="psn" resultMap="personResultMap"> select * from person <where> <choose> <when test="name!=null"> NAME = #{name} </when> <when test="gender!=null"> GENDER = #{gender} </when> <otherwise> IF_IT = #{ifIT} </otherwise> </choose> </where> </select> <!-- foreach --> <select id="selectForeachAge" resultMap="personResultMap"> select * from person where age in <foreach collection="list" item="age" index="i" open="(" close=")" separator=","> #{age} </foreach> </select> </mapper>
package com.mmm.test; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import com.mmm.mapper.PersonMapper; import com.mmm.pojo.Person; public class TestDB { static PersonMapper mapper; static { //直接實例SqlSessionFactoryBuilder對象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //MyBatis配置文件路徑 String path = "mybatis-config.xml"; //經過路徑獲取輸入流 Reader reader = null; try { reader = Resources.getResourceAsReader(path); } catch (IOException e) { e.printStackTrace(); } //經過reader構建sessionFactory SqlSessionFactory sessionFactory = builder.build(reader); //獲取SqlSession對象 SqlSession sqlSession = sessionFactory.openSession(); //獲取Mapper實例 mapper = sqlSession.getMapper(PersonMapper.class); } @Test public void testSelect() throws Exception { Person p = new Person(); //p.setAge(11); //p.setGender("男"); List<Person> list = mapper.selectPerson2(p); for(Person psn:list) { System.out.println(psn); } } @Test public void testUpdate() throws Exception { Person p = new Person(); p.setId(10001); //p.setName("小改2"); //p.setGender("男"); mapper.updatePerson2(p); List<Person> list = mapper.selectAll(); for(Person psn:list) { System.out.println(psn); } } @Test public void testSelectExt() throws Exception { Person p1 = new Person(); //p1.setName("小紅"); //p.setGender("男"); p1.setIfIT("是"); List<Person> list = mapper.selectPersonExt(p1); for(Person psn:list) { System.out.println(psn); } } @Test public void testSelectForeachAge() throws Exception { List<Integer> ageList = new ArrayList<Integer>(); ageList.add(21); ageList.add(25); ageList.add(36); List<Person> list = mapper.selectForeachAge(ageList); for(Person psn:list) { System.out.println(psn); } } }
以上即爲MyBatis動態SQL的內容,在測試類中能夠各類嘗試改變各類輸入值,來查看效果,文中雖然每一個元素都涉及到了,可是有些地方還存在不足,並未過多深刻擴展,例如最後的foreach,咱們的參數不必定是單個,並且也不必定是集合,這些狀況咱們都該怎麼處理,按本身的須要再去深刻學習和了解,每每很快會有深入印象。一個問題與方法的前後問題,當遇到問題後,順着問題去找方法以後,每每很好記住。反之,沒有問題和應用場景,單純的學習方法和理論效果應該會遜色一些。最後首要仍是把這些基礎的東西搞懂,再去慢慢延伸。