封面:洛小汐
做者:潘潘java
若不是生活所迫,誰願意揹負一身才華。mysql
上節咱們介紹了 《 Mybatis系列全解(四):全網最全!Mybatis配置文件 XML 全貌詳解 》,內容很詳細( 也很枯燥),因爲篇幅實在過於冗長,我預計你們想看完得花上兩段上班地鐵公交車的時間 。。。git
不過應該有讓你們瞭解到 Mybatis 的核心配置文件 config.xml 全貌,其中的 <mappers></mappers> 元素便是咱們本節準備登場介紹的 SQL 映射器,上節有介紹了三種引入 SQL 映射器的方式,本節咱們就主要聊聊它的幾個頂級元素用法。github
Mybatis 真正強大就在於它的語句映射,這是它的魔力所在,也是基石。因爲它異常強大,映射器的 XML 文件就顯得相對簡單。若是拿它跟具備相同功能的 JDBC 代碼進行對比,你會當即發現省掉了將近 95% 的代碼( 95% 是Mybatis 官網的說法 ,我也就引入一下 ),MyBatis 致力於減小使用成本,讓用戶能更專一於 SQL 代碼。redis
Mybatis系列全解腦圖分享,持續更新中算法
一、mapper 映射器頂級元素全貌sql
二、namespace 命名空間數據庫
三、select 查詢數組
四、insert / update / delete 增刪改緩存
五、cache 緩存
六、cache-ref 緩存引用
七、sql 語句塊
八、parameterMap 參數映射
九、總結
與其它 ORM 框架如 Hibernate 不一樣,Mybatis 的框架思想但願開發者可以直接操做數據庫編寫 SQL,而不是隱藏起來,讓開發者獨自面對 Java 對象,爲此 Mybatis 設計了 SQL 映射器,任你五招十二式。
映射器有九大頂級元素 ,基本技能介紹
其中,增刪改查操做拼接 SQL 時使用到的 動態SQL( if、where、foreach啥的),以及封裝結果集時使用到的 複雜映射 (1對1 ,1對多,多對多啥的),這兩部分咱們後面單立文章再詳細介紹,本文中咱們簡單點過。
九大頂級元素 ,功能歸類:
其中頂一元素 parameterMap 已建議棄用了 。
不管你有多麼複雜的 SQL 操做,最根本的思路都逃不出以上 4 部分。
一個完整的 Mapper 映射文件,須要有約束頭 xml 與 !DOCTYPE ,其次纔是 mapper 根元素,最後再是頂級元素,而其中,namespace 屬性做爲 mapper 的惟一標識,試回憶:
每一段 SQL 語句都是惟必定義的,咱們在 Mybatis 中用「 命名空間標識 + 語句塊 ID 」做爲惟一的標識,組合以後在 Mybatis 二級緩存中能夠做爲本地 map 集合 緩存 的惟一Key ,也能夠用於 Dao 接口的 映射 綁定,還能做爲惟一 代理 標識。總之,咱們但願避免命名衝突和重複定義,因此,擁有這麼一個惟一標識 ,它就至少有一億個利好。
select 查詢語句,幾乎是咱們最高頻的使用元素,因此 Mybatis 在這塊沒少下功夫,目的就是經過提供儘量多的便利,讓咱們的查詢操做變得簡單。 一個查詢用戶 User 的查詢語句能夠這麼編寫:
<select id="selectUser" parameterType="int" resultType="hashmap"> select * from t_user where id = #{id} </select>
固然若是你不但願經過 hashmap 來接收查詢結果,容許你自由指定返回類型。Mybatis 是支持自動綁定 JavaBean 的,咱們只要讓查詢返回的字段名和 JavaBean 的屬性名保持一致(或者採用駝峯式命名),即可以自動映射結果集,例如你建立一個 Java 類 User.java ,包含兩個屬性 id 和 name , 那麼結果集能夠指定爲 com.vo.User ,就完成了。
<select id="selectUser" parameterType="int" resultType="com.vo.User"> select * from t_user where id = #{id} </select>
注意參數符號:
#{id}
#{} 告訴 MyBatis 建立一個預編譯語句(PreparedStatement)參數,在 JDBC 中,這樣的一個參數在 SQL 中會由一個 「 ? 」 來標識,並被傳遞到一個新的預編譯語句中,就像這樣:
// 近似的 JDBC 代碼,非 MyBatis 代碼... String selectUser = " select * from t_user where id = ? "; PreparedStatement ps = conn.prepareStatement(selectUser); ps.setInt(1,id);
#{} 做爲佔位符,${} 做爲替換符,二者沒有孰輕孰重,只不過應用場景不一樣,適當取捨便可。
咱們但願完成相似 JDBC 中的 PrepareStatement 預編譯處理 ,可使用 #{} ,它會在替換佔位符時首尾添加上單引號 '' ,能有效防止 SQL 注入 風險。
例如使用 ${} 操做刪除 ( 就頗有問題!)
// 一、使用 ${} 有注入風險 delete from t_user where id = ${id} // 二、正常傳值,id 傳入 1 delete from t_user where id = 1 // 結果刪除了id=1 的記錄 // 三、注入風險,id 傳入 1 or 1=1 delete from t_user where id = 1 or 1=1 // 全表刪除了
再看看 #{} 是如何規避 SQL 注入 的:
// 一、使用 #{} 有效防止注入風險 delete from t_user where id = #{id} // 二、正常傳值,id 傳入 1 delete from t_user where id = '1' // 結果刪除了id=1 的記錄 // 三、注入風險,id 傳入 1 or 1=1 delete from t_user where id = '1 or 1=1' // SQL 語句報錯,表數據安全
雖然在防止 SQL 注入方面,${} 確實無能爲力,不過咱們 ${} 在其它方面可不容小覷,例如它容許你靈活地進行 動態表和動態列名的替換 操做,例如:
// 一、靈活查詢指定表數據 select * from ${tableName} // 傳入 tableName參數 = t_user , 結果 select * from t_user // 二、靈活查詢不一樣列條件數據 select * from t_user where ${colunmName} = ${value} // 傳入 colunmName參數 = name , value參數 = '潘潘', 結果 select * from t_user where name = '潘潘' // 傳入 colunmName參數 = id , value參數 = 1, 結果 select * from t_user where id = 1
以上的 ${} 替換列名與表名的方式很是靈活,不過確實存在 SQL 注入風險,因此在考慮使用 #{} 或 ${} 前,須要評估風險,避免風險,容許的狀況下,我建議使用 #{} 。
固然,select 元素容許你配置不少屬性來配置每條語句的行爲細節。
<select id="selectUser" parameterType="int" parameterMap="deprecated" resultType="hashmap" resultMap="personResultMap" flushCache="false" useCache="true" timeout="10" fetchSize="256" statementType="PREPARED" resultSetType="FORWARD_ONLY" databaseId="mysql" resultOrdered="false" resultSets="rs1,rs2,rs3"> select * from t_user </select>
下面詳細介紹一下,略微冗長,一口氣看完吧:
id 必填項,在命名空間下的惟一標識,可被 Mybatis 引用,若是存在相同的 「 命名空間 + 語句id 」 組合,Mybatis 將拋出異常;
parameterType 可選項,傳入語句的參數的類全限定名或別名,能夠是基本類型、map 或 JavaBean 等複雜的參數類型傳遞給 SQL;
parameterMap 用於引用外部 parameterMap 的屬性塊,目前已被廢棄。之後請使用行內參數映射和 parameterType 屬性。
resultType 可選項,定義類的全路徑,在容許自動匹配的狀況下,結果集將經過 Javaben 的規範映射,或定義爲 int 、double、float 等參數;也可使用別名,可是要符合別名規範和定義。 resultType 和 resultMap 之間只能同時使用一個。(平常中,好比咱們統計結果總條數的時候能夠設置爲 int );
resultMap 可選項,對外部 resultMap 的命名引用。結果映射是 MyBatis 最強大的特性,若是你對其理解透徹,許多複雜的映射問題都能迎刃而解,後面一對1、一對多、多對多咱們會有一篇文章單獨講解。 resultType 和 resultMap 之間只能同時使用一個。
flushCache 可選項,清空緩存,將其設置爲 true 後,只要語句被調用,都會致使本地緩存和二級緩存被清空,默認值:false。
useCache 可選項,使用緩存,將其設置爲 true 後,將會致使本條語句的結果被二級緩存緩存起來,默認值:對 select 元素爲 true。
timeout 可選項,這個設置是在拋出異常以前,驅動程序等待數據庫返回請求結果的秒數。默認值爲未設置(unset)(依賴數據庫驅動)。
fetchSize 可選項,獲取記錄的總條數設定。這是一個給驅動的建議值,嘗試讓驅動程序每次批量返回的結果行數等於這個設置值。 默認值爲未設置(unset)(依賴驅動)。因爲性能問題,建議在 sql 作分頁處理。
statementType 可選項,可選 STATEMENT,PREPARED 或 CALLABLE。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認值:PREPARED。
resultSetType 可選項,FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等價於 unset) 中的一個,默認值爲 unset (依賴數據庫驅動)。
咱們知道 JDBC 經過 ResultSet 來對查詢結果進行封裝,ResultSet 對象自己包含了一個由查詢語句返回的一個結果集合。例如你常常在 JDBC 見過的結果集讀取:
// 容許滾動遊標索引結果集 while( rs.next() ){ rs.getString("name"); } // 固然也支持遊標定位到最後一個位置 rs.last(); // 向後滾動 rs.previous();
databaseId 可選項,若是配置了數據庫廠商標識(databaseIdProvider),MyBatis 會加載全部不帶 databaseId 或匹配當前 databaseId 的語句;若是帶和不帶的語句都有,則不帶的會被忽略。
resultOrdered 可選項,這個設置僅針對嵌套結果 select 語句:若是爲 true,將會假設包含了嵌套結果集或是分組,當返回一個主結果行時,就不會產生對前面結果集的引用。 這就使得在獲取嵌套結果集的時候不至於內存不夠用。默認值:false。
數據變動語句 insert,update 和 delete 的實現很是接近,並且相對於 select 元素而言要簡單許多。
<insert id="insertUser" parameterType="domain.vo.User" flushCache="true" statementType="PREPARED" keyProperty="" keyColumn="" useGeneratedKeys="" timeout="20"> <update id="updateUser" parameterType="domain.vo.User" flushCache="true" statementType="PREPARED" timeout="20"> <delete id="deleteUser" parameterType="domain.vo.User" flushCache="true" statementType="PREPARED" timeout="20">
其中大部分屬性和 select 元素相同,咱們介紹 3 個不一樣的屬性:
useGeneratedKeys : (僅適用於 insert 和 update)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數據庫內部生成的主鍵(好比:像 MySQL 和 SQL Server 這樣的關係型數據庫管理系統的自動遞增字段),默認值:false。
keyProperty : (僅適用於 insert 和 update)指定可以惟一識別對象的屬性,MyBatis 會使用 getGeneratedKeys 的返回值或 insert 語句的 selectKey 子元素設置它的值,默認值:未設置(unset
)。若是生成列不止一個,能夠用逗號分隔多個屬性名稱。
咱們先看看 insert,update 和 delete 語句的示例:
<insert id="insertUser"> insert into t_user (id,name) values (#{id},#{name}) </insert> <update id="updateUser"> update t_user set name = #{name} where id = #{id} </update> <delete id="deleteUser"> delete from t_user where id = #{id} </delete>
如前所述,插入語句的配置規則更加豐富,在插入語句裏面有一些額外的屬性和子元素用來處理主鍵的生成,而且提供了多種生成方式。
首先,若是你的數據庫支持 自動生成主鍵 的字段(好比 MySQL 和 SQL Server),那麼你能夠設置 useGeneratedKeys=」true」,而後再把 keyProperty 設置爲目標屬性就 OK 了。例如,若是上面的 t_user 表已經在 id 列上使用了自動生成,那麼語句能夠修改成:
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> insert into t_user (name) values (#{name}) </insert>
若是你的數據庫還支持多行插入, 你也能夠傳入一個 User 數組或集合,並返回自動生成的主鍵。
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> insert into t_user (name) values <foreach item="item" collection="list" separator=","> (#{item.name}) </foreach> </insert>
對於不支持自動生成主鍵列的數據庫和可能不支持自動生成主鍵的 JDBC 驅動,MyBatis 有另一種方法來生成主鍵。
這裏有一個簡單(也很傻)的示例,它能夠生成一個隨機 ID(不建議實際使用,這裏只是爲了展現 MyBatis 處理問題的靈活性和寬容度):
<insert id="insertUser"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1 </selectKey> insert into t_user (id, name) values (#{id}, #{name}) </insert>
在上面的示例中,首先會運行 selectKey 元素中的語句,並設置 User 的 id,而後纔會調用插入語句。這樣就實現了數據庫自動生成主鍵相似的行爲,同時保持了 Java 代碼的簡潔。
selectKey 元素描述以下:
<selectKey keyProperty="id" resultType="int" order="BEFORE" statementType="PREPARED">
selectKey 中的 order 屬性有2個選擇:BEFORE 和 AFTER 。
緩存對於互聯網系統來講特別常見,其特色就是將數據保存在內存中。MyBatis 內置了一個強大的事務性查詢緩存機制,它能夠很是方便地配置和定製。 爲了使它更增強大並且易於配置,咱們對 MyBatis 3 中的緩存實現進行了許多改進。
默認狀況下,只啓用了本地的會話緩存(即一級緩存,sqlSession級別 ),它僅僅對一個會話中的數據進行緩存。 要啓用全局的二級緩存,首先在全局配置文件config.xml文件中加入以下代碼:
<!--開啓二級緩存--> <settings> <setting name="cacheEnabled" value="true"/> </settings>
其次在UserMapper.xml文件中開啓緩存:
<!--開啓二級緩存--> <cache></cache>
基本上就是這樣。這個簡單語句的效果以下:
緩存只做用於 cache 標籤所在的映射文件中的語句。若是你混合使用 Java API 和 XML 映射文件,在共用接口中的語句將不會被默認緩存。你須要使用 @CacheNamespaceRef 註解指定緩存做用域。
這些屬性能夠經過 cache 元素的屬性來修改。好比:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
上面表示了一套更高級的緩存配置,首先建立了一個 FIFO 緩存,每隔 60 秒刷新,最多能夠存儲結果對象或列表的 512 個引用,而後返回的對象被設置成只讀的,所以對它們進行修改可能會在不一樣線程中的調用者產生衝突。
緩存可用的清除策略有:
默認的清除策略是 LRU
flushInterval(刷新間隔)屬性能夠被設置爲任意的正整數,設置的值應該是一個以毫秒爲單位的合理時間量。 默認狀況是不設置,也就是沒有刷新間隔,緩存僅僅會在調用語句時刷新。
size(引用數目)屬性能夠被設置爲任意正整數,要注意欲緩存對象的大小和運行環境中可用的內存資源。默認值是 1024。
readOnly(只讀)屬性能夠被設置爲 true 或 false。只讀的緩存會給全部調用者返回緩存對象的相同實例。 所以這些對象不能被修改。這就提供了可觀的性能提高。而可讀寫的緩存會(經過序列化)返回緩存對象的拷貝。 速度上會慢一些,可是更安全,所以默認值是 false。
二級緩存是事務性的。這意味着,當 SqlSession 完成並提交 ( commit ) 時,或是完成並回滾 ( close ) 時,二級緩存都會被刷新。無論是否配置了 flushCache=true 。
Mybatis 的緩存包括一級緩存(sqlSession 級別)和二級緩存(mapper 級別),因此 mapper 映射器中配置的是二級緩存,咱們先大概知道有這個概念,由於後續咱們會針對這兩種緩存進行詳細介紹,並且還會講解如何自定義緩存,由於 Mybatis 的緩存默認都是以 map 的數據結構存儲在本地,因此自定義緩存能夠把存儲介質拓展到磁盤或數據庫redis等;並且一級緩存是默認開啓的,二級緩存須要咱們手工開啓,這些後續都會詳細講解,提早預告。
緩存獲取順序:二級緩存 > 一級緩存 > 數據庫
回想一下 cache 的內容,對某一命名空間的語句,只會使用該命名空間的緩存進行緩存或刷新。 但你可能會想要在多個命名空間中共享相同的緩存配置和實例。要實現這種需求,你可使用 cache-ref 元素來引用另外一個緩存。
<cache-ref namespace="com.vo.UserMapper"/>
這個元素能夠用來定義可重用的 SQL 代碼片斷,以便在其它語句中使用。 參數能夠靜態地(在加載的時候)肯定下來,而且能夠在不一樣的 include 元素中定義不一樣的參數值。好比:
<sql id="userColumns"> ${alias}.id,${alias}.name </sql>
這個 SQL 片斷能夠在其它語句中使用,例如:
<select id="selectUsers" resultType="map"> select <include refid="userColumns"> <property name="alias" value="t1"/> </include>, <include refid="userColumns"> <property name="alias" value="t2"/> </include> from t_user t1 cross join t_user t2 </select>
也能夠在 include 元素的 refid 屬性或多層內部語句中使用屬性值,例如:
<sql id="sql1"> ${prefix}_user </sql> <sql id="sql2"> from <include refid="${include_target}"/> </sql> <select id="select" resultType="map"> select id, name <include refid="sql2"> <property name="prefix" value="t"/> <property name="include_target" value="sql1"/> </include> </select>
parameterMap 元素官方已經不建議使用,而且再後續版本會退出舞臺。首先對於咱們 Java 來講,特別不但願在代碼中經過傳遞 map 來傳參,這樣對於後續維護或者參數查找都是極不負責任的,咱們推薦使用 JavaBean 來傳值參數,這是 parameterMap 被拋棄的其中一個緣由;另外也因爲 parameterType 屬性的誕生就能很好的代替 parameterMap ,而且還能自定義 JavaBean 類型的傳參,因此 parameterMap 退出舞臺,實屬正常。
我一直來都但願本身只輸出觀點,而不是輸出字典,但其中有些知識點又是極其冗雜,知識輸出真是個難搞的差事,如何既能把知識脈絡梳理的完整,又能講得淺顯易懂,言簡意賅,確實是後續文章分解輸出的研究方向。
本篇完,本系列下一篇咱們講《 Mybatis系列全解(六):Mybatis最硬核的API你知道幾個? 》。
BIU ~ 文章持續更新,微信搜索「潘潘和他的朋友們」第一時間閱讀,隨時有驚喜。本文會在 GitHub https://github.com/JavaWorld 收錄,熱騰騰的技術、框架、面經、解決方案,咱們都會以最美的姿式第一時間送達,歡迎 Star ~ 咱們將來 不止文章!想進讀者羣的夥伴歡迎撩我我的號:panshenlian,備註「加羣」咱們羣裏歡聊吧 ~