上一篇文章,我們學習了使用XML實現MyBatis進行增、刪、查、改等操作,本篇文章,我們將學習#{ }和${ }獲取方法參數的區別和使用MyBatisXML實現動態SQL語句。
#{ }和${ }的區別
在之前的文章中我們都是使用#{ }進行賦值,但實際上Mybatis也支持${ } 對參數進行賦值。
Integer類型的參數
我們先來看Integer類型的參數的賦值:
mapper:
//Integer@Select("select * from userinfo where id = #{id} ")List<Userinfo> getById(Integer id);@Select("select * from userinfo where id = ${id} ")List<Userinfo> getById2(Integer id);
測試:
@Testvoid getById() {System.out.println(mapper.getById(1));}@Testvoid getById2() {System.out.println(mapper.getById(1));}
#{ }:?
${ }:?
可以看到查詢到的結果是一樣的,都能夠正常查到。此時,我們再換一個String類型試試。
String類型的參數
mapper:
//String@Select("select * from userinfo where username = #{username} ")List<Userinfo> getByName(String username);@Select("select * from userinfo where username = ${username} ")List<Userinfo> getByName2(String username);
測試:
@Testvoid getByName() {System.out.println(mapper.getByName("xmy"));}@Testvoid getByName2() {System.out.println(mapper.getByName2("xmy"));}
#{ }:?
測試通過。?
?${ }:
可以看到報出BadSql異常。
此時觀察日志,我們傳過去的sql語句是這樣的:通過觀察,我們發現xmy處少了引號,正確的sql語句是‘xmy’。我們手動給sql語句加上引號,修改代碼如下:?
@Select("select * from userinfo where username = '${username}' ")List<Userinfo> getByName2(String username);
測試通過。
通過對比#{ }和${ }打出來的日志我們發現#{ }是預編譯sql(通過?占位的方式,提前對sql進行編譯然后把參數填充到SQL語句中),#{ }會根據參數類型自動拼接引號;而${ }則是直接進行字符替換,一起對SQL進行編譯(即時sql)。如果參數為字符串,需要加上引號。?
在開發環境下,我們能使用#{ }就不要使用${ }。不僅僅因為#{ }的效率比${ }更高,而是因為${ }可能會產生sql注入的問題。
那么什么是sql注入呢?下面我們通過代碼來演示sql注入。
${ }引發sql注入問題
sql注入:通過操作輸入的數據來修改事先定義好的sql語句,以達到執行代碼對服務器進行攻擊的方法。
我們先嘗試在數據庫中使用下面的sql語句進行查詢:
SELECT * FROM `userinfo` where username = ' OR 1 = '1;
?可以看到此時我們的代碼是有問題的:
下面我們使用#{ }和${ }?分別進行查詢:
//String@Select("select * from userinfo where username = #{username} ")List<Userinfo> getByName(String username);@Select("select * from userinfo where username = '${username}' ")List<Userinfo> getByName2(String username);
?測試:
@Testvoid getByName() {System.out.println(mapper.getByName("' OR 1 = '1"));}@Testvoid getByName2() {System.out.println(mapper.getByName2("' OR 1 = '1"));}
#{ }:?
${ }:?可以看到,${ }依然正常查詢出來了,其中參數or被當作了SQL語句的一部分:
${ }的作用
從上面的例子中,我們可以知道:${}會有sql注入的風險,所以我們盡量使用#{}完成查詢。
既然如此,${ }是不是就沒有存在的必要了呢?
當然不是。接下來我們通過代碼來看看${ }的作用。
1、使用${}實現排序功能
mapper:?
@Select("select * from userinfo order by id ${order}")List<Userinfo> getByOrder(String order);
測試:?
@Testvoid getByOrder() {System.out.println(mapper.getByOrder("desc"));}
可以看到,能夠根據id逆序輸出結果。此時我們將${ }改成#{ }?。
@Select("select * from userinfo order by id #{order}")List<Userinfo> getByOrder(String order);
測試結果:?
可以發現,當使用#{sort}查詢時,desc前后加上了引號,導致sql錯誤。
2、使用${ }實現模糊查詢
?mapper:
@Select("select * from userinfo where username like '%${username}%'")List<Userinfo> getByLike(String username);
測試:?
@Testvoid getByLike() {System.out.println(mapper.getByLike("zhangsan"));}
此時我們將${ } 改為#{ }?:
@Select("select * from userinfo where username like '%#{username}%'")List<Userinfo> getByLike(String username);
可以看到,依然是因為引號的關系,出現了異常。?
但是在模糊查詢中使用#{ }也有解決辦法 :我們可以使用concat()來拼接字符串:
@Select("select * from userinfo where username like concat('%',#{username},'%')")List<Userinfo> getByLike1(String username);
測試:?
@Testvoid getByLike1() {System.out.println(mapper.getByLike1("zhangsan"));}
總結: #{ }和${ }的區別
1、#{ }和${ }的區別就是預編譯sql(占位)和即時sql(直接拼接)的區別。
2、#{ }使用預編譯的形式所以性能會比${ }更高
絕大多數情況下,某一條sql語句可能會被反復調用執行,或者每次執行的時候只有個別的值不同(比如select的where子句值不同,insert的values值不同)。如果每次都需要上面語法解析,sql優化,sql編譯等過程,效率就明顯不行了。
預編譯sql,編譯一次之后會將編譯后的sql語句緩存起來,后面再次執行這條語句時,不會再次編譯(只是輸入的參數不同),省去了解析優化等過程,以此來提高效率。
3、#{ }更安全(防止sql注入)
在使用${ }的場景下一定一定要考慮到sql注入問題,并采取措施進行防止:例如:1、在接口層(Controller層)進行判定,如果輸入的結果不是我們想要的直接返回。2、直接給接口寫死,根據用戶輸入的內容來決定調用哪個接口,如果沒有接口符合,則返回。
4、在一些場景下仍然需要用到${ }
比如:排序,參數不需要引號……?
數據庫連接池
在MyBtis中,我們使用了數據庫連接池技術,避免頻繁地創建銷毀連接。
數據庫連接池負責分配、管理和釋放數據庫連接,它允許應用程序重復使用一個現有地數據庫連接,而不是重新建立一個。?
沒有使用數據庫連接池的情況:每次執行sql語句,要先創建一個新的連接對象,然后執行sql語句,sql語句執行完,再關閉連接對象釋放資源。這種重復的創建連接,銷毀連接比較消耗資源。
使用數據庫連接池的情況:程序啟動時,會在數據庫連接池中創建一定數量的Connection對象,當客戶請求數據庫連接池,會從數據庫連接池中獲取Connection對象,然后執行sql,sql語句執行完,再把Connection歸還給連接池。
優點:
1、減少了網絡開銷
2、資源重用
3、提升了系統性能?
動態SQL
動態sql是mybaatis的強大特性之一,能夠完成不同條件下不同的sql拼接。
官方文檔:動態 SQL_MyBatis中文網
?<if>標簽
在注冊用戶的時候,可能會有這樣一個問題,如下圖所示:
注冊分為兩種字段:必填字段和非必填字段,那如果在添加用戶的時候有不確定的字段傳入,程序應該如何實現呢?
這時候就需要使用動態標簽進行判斷了,如添加的時候性別為非必填字段,具體實現如下(XML實現):?
Mapper:
Integer insertBatch(Userinfo userinfo);
XML:
<insert id="insertBatch">insert into userinfo (username,password,age<if test="gender!=null">,gender</if>)values (#{username},#{password},#{age}<if test="gender!=null">,#{gender}</if>)</insert>
測試(有性別):?
@Testvoid insertBatch() {Userinfo userinfo = new Userinfo();userinfo.setUsername("lisi");userinfo.setAge(16);userinfo.setPassword("lisi666");userinfo.setGender(2);mapper.insertBatch(userinfo);}
測試(無性別):?
@Testvoid insertBatch() {Userinfo userinfo = new Userinfo();userinfo.setUsername("lisi");userinfo.setAge(16);userinfo.setPassword("lisi666");mapper.insertBatch(userinfo);}

?if標簽詳解:
<trim>標簽
mapper:
Integer insertBatch2(Userinfo userinfo);
假如我們有許多的元素需要選填,那么我們此時的XML語句會變成這樣:
<insert id="insertBatch2">insert into userinfo(<if test="username != null">username</if><if test="password != null">,password</if><if test="age != null">,age</if><if test="gender != null">,gender </if>)values (<if test="username != null">#{username}</if><if test="password != null">,#{password}</if><if test="age != null">,#{age}</if><if test="gender != null">,#{gender}</if>)</insert>
我們測試的時候選填其中兩個參數(性別,年齡):?
@Testvoid insertBatch2() {Userinfo userinfo = new Userinfo();userinfo.setGender(1);userinfo.setAge(16);mapper.insertBatch2(userinfo);}
?測試不通過,觀察報錯日志發現是因為<if>標簽的原因,導致我們sql語句多加了個逗號。
那么我們能不能把逗號加在后面呢?同樣的,如果將逗號加在后面,那么后面也會多一個逗號。
下面我們使用<trim>標簽來解決問題:
<insert id="insertBatch2">insert into userinfo<trim prefix="(" suffix=")" prefixOverrides=","><if test="username != null">username</if><if test="password != null">,password</if><if test="age != null">,age</if><if test="gender != null">,gender</if></trim>values <trim prefix="(" suffix=")" prefixOverrides=","> <if test="username != null">#{username}</if><if test="password != null">,#{password}</if><if test="age != null">,#{age}</if><if test="gender != null">,#{gender}</if></trim></insert>
?測試通過:
那么<trim>標簽的作用是什么呢??
<trim>標簽詳解:
<where>標簽
我們在淘寶上逛東西時,通常會有一些按鈕能夠動態組裝我們的查詢條件。
這種功能如何實現呢??
1、通過上面的<trim>標簽和<if>標簽實現
mapper:
List<Userinfo> queryByConditin(Userinfo userinfo);
XML:?
<select id="queryByConditin" resultType="com.example.mybatis.model.Userinfo">select * from userinfo<trim prefix="where" prefixOverrides="and"><if test="username != null">username = #{username}</if><if test="age != null">and age = #{age}</if><if test="gender != null">and gender = #{gender}</if><if test="password != null">and password = #{password}</if></trim></select>
測試(查詢姓名為:“lisi” 年齡為:16的用戶):?
@Testvoid queryByConditin() {Userinfo userinfo = new Userinfo();userinfo.setUsername("lisi");userinfo.setAge(16);System.out.println(mapper.queryByConditin(userinfo));
?這種查詢方法固然能夠成功,但是并不專業而且如果我們不添加任何查詢條件時sql語句會多出來一個where導致Badsql異常,我們可以使用where標簽來代替<trim>標簽。
2、使用<where>標簽實現
XML代碼:?
<select id="queryByConditin" resultType="com.example.mybatis.model.Userinfo">select * from userinfo<where><if test="username != null">username = #{username}</if><if test="age != null">and age = #{age}</if><if test="gender != null">and gender = #{gender}</if><if test="password != null">and password = #{password}</if></where></select>
測試:?
測試不加任何條件:
@Testvoid queryByConditin() {Userinfo userinfo = new Userinfo();System.out.println(mapper.queryByConditin(userinfo));}
可以發現我們傳入的sql語句并沒有where。?
<where>標簽詳解:
<set>標簽
與選擇查詢相同有時候我們也要更新一些用戶的選項值,而保證其他值不變,比如:用戶修改手機號、用戶修改密碼等。
同樣的,這一功能也能通過<trim>標簽和<if>標簽實現:
mapper:
Integer updateByConditin(Userinfo userinfo);
XML:
<update id="updateByConditin">update userinfo<trim prefix="set" suffixOverrides=","><if test="username != null">username = #{username},</if><if test="age != null">age = #{age},</if><if test="gender != null">gender = #{gender},</if><if test="password != null">password = #{password},</if></trim>where id = #{id}</update>
測試(修改id為6的用戶名和密碼):?
@Testvoid updateByConditin() {Userinfo userinfo = new Userinfo();userinfo.setPassword("123456");userinfo.setUsername("wangwu");userinfo.setId(6);mapper.updateByConditin(userinfo);}
同樣的,我們也可以使用<set>標簽來代替這里的<trim>標簽和<if>標簽:?
<update id="updateByConditin">update userinfo<set><if test="username != null">username = #{username},</if><if test="age != null">age = #{age},</if><if test="gender != null">gender = #{gender},</if><if test="password != null">password = #{password},</if></set>where id = #{id}</update>
?測試(修改id為7的用戶名和密碼):
@Testvoid updateByConditin() {Userinfo userinfo = new Userinfo();userinfo.setPassword("666666");userinfo.setUsername("wuwuwu");userinfo.setId(7);mapper.updateByConditin(userinfo);}