1. 程式人生 > >MyBatis核心對映器學習

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">
Insert,Update,Delete'sAttributes
屬性 描述
id 名稱空間中的唯一識別符號,可被用來代表這條語句。
parameterType 將要傳入語句的引數的完全限定類名或別名。這個屬性是可選的,因為MyBatis可以通過TypeHandler推斷出具體傳入語句的引數,預設值為unset。
parameterMap 這是引用外部parameterMap的已經被廢棄的方法。使用內聯引數對映和parameterType屬性。
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">
SelectAttributes
屬性 描述
id 在名稱空間中唯一的識別符號,可以被用來引用這條語句。
parameterType 將會傳入這條語句的引數類的完全限定名或別名。這個屬性是可選的,因為MyBatis可以通過TypeHandler推斷出具體傳入語句的引數,預設值為unset。
parameterMap 這是引用外部parameterMap的已經被廢棄的方法。使用內聯引數對映和parameterType屬性。
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>

lazyLoadingEnableaggressiveLazyLoading這兩個屬性預設是關閉的。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>