MyBatis核心對映器學習
一、概述
MyBatis 的真正強大在於它的對映語句,也是它的魔力所在。由於它的異常強大,對映器的 XML 檔案就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 程式碼進行對比,你會立即發現省掉了將近 95% 的程式碼。MyBatis 就是針對 SQL 構建的,並且比普通的方法做的更好。
對映器的配置元素:
元素名稱 | 描述 | 備註 |
---|---|---|
insert | 插入語句 | 執行後返回一條整數,代表插入的條數 |
delete | 刪除語句 | 執行後返回一條整數,代表刪除的條數 |
update | 更新語句 | 執行後返回一條整數,代表更新的條數 |
select | 查詢語句 | 可自定義引數,返回結果集 |
sql | 先定義一部分SQL,可以被引用 | 如:一個表的列名可單獨定義,多次在不同的語句中使用 |
resultMap | 描述資料庫結果集中載入的物件,複雜又強大 | 停供自定義對映規則 |
cache | 給定名稱空間的快取配置。 | |
cache-ref | 其他名稱空間快取配置的引用。 |
二、insert、update、delete元素
元素及屬性
<insert id="insertAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" keyProperty="" keyColumn="" useGeneratedKeys="" timeout="20"> <update id="updateAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20"> <delete id="deleteAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20">
屬性 | 描述 |
---|---|
id | 名稱空間中的唯一識別符號,可被用來代表這條語句。 |
parameterType | 將要傳入語句的引數的完全限定類名或別名。這個屬性是可選的,因為MyBatis可以通過TypeHandler推斷出具體傳入語句的引數,預設值為unset。 |
|
|
flushCache | 將其設定為true,任何時候只要語句被呼叫,都會導致本地快取和二級快取都會被清空,預設值:true(對應插入、更新和刪除語句)。 |
timeout | 這個設定是在丟擲異常之前,驅動程式等待資料庫返回請求結果的秒數。預設值為unset(依賴驅動)。 |
statementType | STATEMENT,PREPARED或CALLABLE的一個。這會讓MyBatis分別使用Statement,PreparedStatement或CallableStatement,預設值:PREPARED。 |
useGeneratedKeys | (僅對insert和update有用)這會令MyBatis使用JDBC的getGeneratedKeys方法來取出由資料庫內部生成的主鍵(比如:像MySQL和SQLServer這樣的關係資料庫管理系統的自動遞增欄位),預設值:false。 |
keyProperty | (僅對insert和update有用)唯一標記一個屬性,MyBatis會通過getGeneratedKeys的返回值或者通過insert語句的selectKey子元素設定它的鍵值,預設:unset。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。 |
keyColumn | (僅對insert和update有用)通過生成的鍵值設定表中的列名,這個設定僅在某些資料庫(像PostgreSQL)是必須的,當主鍵列不是表中的第一列的時候需要設定。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。 |
databaseId | 如果配置了databaseIdProvider,MyBatis會載入所有的不帶databaseId或匹配當前databaseId的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。 |
insert語句中的主鍵回填
如果在資料庫中我們把主鍵設為自動增長,在插入資料的時候我們就沒有必要再為主鍵插入值。但有時候我們需要繼續使用這個主鍵,取到它的值為了後面更多的操作。為此MyBatis提供這樣的支援去獲取主鍵。
<insert id="insert" parameterType="com.lzx.entity.PhoneList"
useGeneratedKeys="true" keyColumn="id">
insert into phone_list (phone, name)
values ( #{phone}, #{name})
</insert>
批量插入資料
<insert id="insertAuthor" useGeneratedKeys="true"
keyProperty="id">
insert into Author (username, password, email, bio) values
<foreach item="item" collection="list" separator=",">
(#{item.username}, #{item.password}, #{item.email}, #{item.bio})
</foreach>
</insert>
自定義主鍵
對於不支援自動生成型別的資料庫或可能不支援自動生成主鍵的 JDBC 驅動,MyBatis 有另外一種方法來生成主鍵。
<insert id="insertAuthor">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>
insert into Author
(id, username, password, email,bio, favourite_section)
values
(#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>
selectKey 元素將會首先執行,Author 的 id 會被設定,然後插入語句會被呼叫。這給你了一個和資料庫中來處理自動生成的主鍵類似的行為,避免了使 Java 程式碼變得複雜。
update和delete
<update id="updateByPrimaryKey" parameterType="com.lzx.entity.PhoneList">
update phone_list set phone = #{phone},name = #{name} where id = #{id}
</update>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from phone_list where id = #{id}
</delete>
三、select元素
select 元素有很多屬性允許你配置,來決定每條語句的作用細節。
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10000"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
屬性 | 描述 |
---|---|
id | 在名稱空間中唯一的識別符號,可以被用來引用這條語句。 |
parameterType | 將會傳入這條語句的引數類的完全限定名或別名。這個屬性是可選的,因為MyBatis可以通過TypeHandler推斷出具體傳入語句的引數,預設值為unset。 |
resultType | 從這條語句中返回的期望型別的類的完全限定名或別名。注意如果是集合情形,那應該是集合可以包含的型別,而不能是集合本身。使用resultType或resultMap,但不能同時使用。 |
resultMap | 外部resultMap的命名引用。結果集的對映是MyBatis最強大的特性,對其有一個很好的理解的話,許多複雜對映的情形都能迎刃而解。使用resultMap或resultType,但不能同時使用。 |
flushCache | 將其設定為true,任何時候只要語句被呼叫,都會導致本地快取和二級快取都會被清空,預設值:false。 |
useCache | 將其設定為true,將會導致本條語句的結果被二級快取,預設值:對select元素為true。 |
timeout | 這個設定是在丟擲異常之前,驅動程式等待資料庫返回請求結果的秒數。預設值為unset(依賴驅動)。 |
fetchSize | 這是嘗試影響驅動程式每次批量返回的結果行數和這個設定值相等。預設值為unset(依賴驅動)。 |
statementType | STATEMENT,PREPARED或CALLABLE的一個。這會讓MyBatis分別使用Statement,PreparedStatement或CallableStatement,預設值:PREPARED。 |
resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE或SCROLL_INSENSITIVE中的一個,預設值為unset(依賴驅動)。 |
databaseId | 如果配置了databaseIdProvider,MyBatis會載入所有的不帶databaseId或匹配當前databaseId的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。 |
resultOrdered | 這個設定僅針對巢狀結果select語句適用:如果為true,就是假設包含了巢狀結果集或是分組了,這樣的話當返回一個主結果行的時候,就不會發生有對前面結果集的引用的情況。這就使得在獲取巢狀的結果集的時候不至於導致記憶體不夠用。預設值:false。 |
resultSets | 這個設定僅對多結果集的情況適用,它將列出語句執行後返回的結果集並每個結果集給一個名稱,名稱是逗號分隔的。 |
簡單應用
<select id="selectAll" resultType="com.lzx.entity.PhoneList">
select id, phone, name from phone_list
</select>
傳遞多個引數
//定義接口裡面的方法
//方式一:使用註解
Author queryByNameAndCity(@Param("name") String name,@Param("city") String city);
//方式二:使用實體類
Author queryByNameAndCity(Author author);
<!--xml實現-->
<select id="queryByNameAndCity" resultType="com.lzx.entity.Author">
select * from author where name = #{name} and city = #{city}
</select>
分頁引數RowBounds
MyBatis中的分頁是物理分頁的方式。即先取出所有的物件放到一個集合裡面,當你需要分頁時,從集合中查詢你所需要的結果集。這種方式當資料量較小時可以使用,如果資料量過大,它會佔用過多的記憶體,給電腦造成很大的壓力,所以這種方式不推薦使用。因為市面上有許多的資料庫,MyBatis提供了這種通用的方式。為了提高分頁效率,我們可以使用外掛來實現。
//使用起來十分簡單,只需給介面新增一個RowBounds引數即可
public List<Author> findByPage(@Param("name") String name,
@Param("city") String city,RowBounds rowBounds);
<!--xml實現時不需要rowBounds引數-->
<select id="queryByNameAndCity" resultType="com.lzx.entity.Author">
select * from author where name = #{name} and city = #{city}
</select>
使用ResultMap作為結果集對映
resultMap 元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90% 的 JDBC ResultSets 資料提取程式碼中解放出來, 並在一些情形下允許你做一些 JDBC 不支援的事情。 實際上,在對複雜語句進行聯合對映的時候,它很可能可以代替數千行的同等功能的程式碼。 ResultMap 的設計思想是,簡單的語句不需要明確的結果對映,而複雜一點的語句只需要描述它們的關係就行了。實際開發中ResultMap多用於為多表連線查詢定製結果集。
//定義一個結果集
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
<!--使用結果集-->
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
這段程式碼裡面,type代表使用哪個類作為對映類,可以使用別名或完全限定名。子元素id代表標識列(主鍵),property代表POJO(普通java物件)中的屬性,column代表資料庫裡面的欄位。
sql元素
這個元素可以被用來定義可重用的 SQL 程式碼段,可以包含在其他語句中。它可以被靜態地(在載入引數) 引數化. 不同的屬性值通過包含的例項變化.
簡單使用
<!--定義-->
<sql id="authorColumns">
name,address,phone
</sql>
<!--使用-->
<select id="getAuthor" parameterType="long" resultType="Author">
select <include refid="authorColumns" /> from author where id = #{id}
</select>
傳遞變數給sql元素
<!--定義-->
<sql id="authorColumns">
${alias}.name,${alias}.address,${alias}.phone
</sql>
<!--使用-->
<select id="getAuthor" parameterType="long" resultType="Author">
select
<include refid="authorColumns">
<property name = "alias" value = "a"/>
</include>
from author a where id = #{id}
</select>
瞭解#和$的區別
使用 # 時,代表這是一個引數,類似jdbc中使用prepareStatement通過 ? 來設定引數; 使用 $ 代表這是一個字串,會與sql語句連線起來。
N+1問題
在資料庫中,如果已經完成了N個關聯關係完成了級聯,如果再加入一個關聯東西,就變成了N+1個級聯,所有的SQL都會被執行,有很多的資料都不是我們所需要的資料,造成了資源浪費,這就是N+1問題。
為了應對此問題,MyBatis提供了延遲載入的功能。
延遲載入
延遲載入就是我們一次性把常用的級聯資料通過SQL直接查詢出來,對於不常用的級聯資料只有等到用時採取出,這些不常用的級聯資料就可以採用延遲載入的功能。延遲載入的配置也十分簡單,我們可以在mybatis的配置檔案中進行配置。
<settings>
<setting name="lazyLoadingEnable" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
lazyLoadingEnable和aggressiveLazyLoading這兩個屬性預設是關閉的。lazyLoadingEnable決定是否開啟延遲載入,aggressiveLazyLoading控制是否採用層級載入,它們都是全域性性的配置,有時不能解決我們的需求。除此之外,MyBatis中為我們提供了一個名為fetchType的屬性給我們使用(只能在association、collection中使用),它有兩個屬性:
- eager:獲得當前的POJO後立即載入對應的資料。
- lazy:獲得當前的POJO後延遲載入對應的資料。
使用的前提是開啟lazyLoadingEnabled即可。
四、快取
基本介紹
MyBatis中允許使用快取,分為一級快取和二級快取,你可以根據自己的需求進行配置。預設情況下是沒有開啟快取的,除了區域性的 session 快取,可以增強變現而且處理迴圈 依賴也是必須的。要開啟二級快取,你需要在你的 SQL 對映檔案中新增一行:
<cache/>
字面上看就是這樣。這個簡單語句的效果如下:
- 對映語句檔案中的所有 select 語句將會被快取。
- 對映語句檔案中的所有 insert,update 和 delete 語句會重新整理快取。
- 快取會使用 Least Recently Used(LRU,最近最少使用的)演算法來收回。
- 根據時間表(比如 no Flush Interval,沒有重新整理間隔), 快取不會以任何時間順序 來重新整理。
- 快取會儲存列表集合或物件(無論查詢方法返回什麼)的 1024 個引用。
- 快取會被視為是 read/write(可讀/可寫)的快取,意味著物件檢索不是共享的,而 且可以安全地被呼叫者修改,而不干擾其他呼叫者或執行緒所做的潛在修改。
所有的這些屬性都可以通過快取元素的屬性來修改。比如:
<cache
eviction="FIFO"<!--快取策略 -->
flushInterval="60000"<!--重新整理時間,預設為null,正整數-->
size="512"<!--快取物件數,正整數預設1024-->
readOnly="true"/><!--是否只讀-->
建了一個 FIFO 快取,並每隔 60 秒重新整理,存數結果物件或列表的 512 個引用,而且返回的物件被認為是隻讀的,因此在不同執行緒中的呼叫者之間修改它們會 導致衝突。
可用的收回策略有:
- LRU – 最近最少使用的:移除最長時間不被使用的物件。(預設)
- FIFO – 先進先出:按物件進入快取的順序來移除它們。
- SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的物件。
- WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的物件。
使用自定義快取
cache介面
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
}
自定義快取必須要實現 org.mybatis.cache.Cache 介面。雖然複雜,我們使用時簡單的給定它做什麼即可。
配置自定義快取
<cache type="com.ssm.lzx.cache.RedisCache">
<property name="host" value="localhost"/>
</cache>
預設情況下,語句可以這樣來配置:
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
flushCache代表是否重新整理快取,對於select、insert、update、delete都有效。useCache是select特有的,代表是否使用快取。
五、儲存過程
MyBatis對儲存過程也提供了支援,對於儲存過程的3種引數:**輸入(IN)、輸出(OUT)、輸入輸出(INOUT)**提供了支援。對於這三種引數,MyBatis已近為我們定義好了:
- #{id,mode=IN}
- #{name,mode=OUT}
- #{sex,mode=INOUT}
簡單呼叫
<!--statementType的引數告訴MyBatis,我們呼叫了一個儲存過程-->
<select id="getCustomer" parameterType="com.ssm.lzx.Customer" statementType="CALLABLE">
{call get_customer(
#{id, mode=IN, jdbcType=INTEGER},
#{name, mode=OUT, jdbcType=VARCHAR},
#{sex, mode=INOUT, jdbcType=VARCHAR}
)}
</select>