1. 程式人生 > >mybatis入門(三)之Mapper XML 檔案

mybatis入門(三)之Mapper XML 檔案

Mapper XML 檔案

MyBatis 的真正強大在於它的對映語句,也是它的魔力所在。由於它的異常強大,對映器的 XML 檔案就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 程式碼進行對比,你會立即發現省掉了將近 95% 的程式碼。MyBatis 就是針對 SQL 構建的,並且比普通的方法做的更好。

SQL 對映檔案有很少的幾個頂級元素(按照它們應該被定義的順序):

  • cache – 給定名稱空間的快取配置。
  • cache-ref – 其他名稱空間快取配置的引用。
  • resultMap – 是最複雜也是最強大的元素,用來描述如何從資料庫結果集中來載入物件。
  • parameterMap
     – 已廢棄!老式風格的引數對映。內聯引數是首選,這個元素可能在將來被移除,這裡不會記錄。
  • sql – 可被其他語句引用的可重用語句塊。
  • insert – 對映插入語句
  • update – 對映更新語句
  • delete – 對映刪除語句
  • select – 對映查詢語句

下一部分將從語句本身開始來描述每個元素的細節。

select

查詢語句是 MyBatis 中最常用的元素之一,光能把資料存到資料庫中價值並不大,如果還能重新取出來才有用,多數應用也都是查詢比修改要頻繁。對每個插入、更新或刪除操作,通常對應多個查詢操作。這是 MyBatis 的基本原則之一,也是將焦點和努力放到查詢和結果對映的原因。簡單查詢的 select 元素是非常簡單的。比如:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

這個語句被稱作 selectPerson,接受一個 int(或 Integer)型別的引數,並返回一個 HashMap 型別的物件,其中的鍵是列名,值便是結果行中的對應值。

注意引數符號:

#{id}

這就告訴 MyBatis 建立一個預處理語句引數,通過 JDBC,這樣的一個引數在 SQL 中會由一個“?”來標識,並被傳遞到一個新的預處理語句中,就像這樣:

// Similar JDBC code, NOT MyBatis…
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

當然,這需要很多單獨的 JDBC 的程式碼來提取結果並將它們對映到物件例項中,這就是 MyBatis 節省你時間的地方。我們需要深入瞭解引數和結果對映,細節部分我們下面來了解。

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">
Select Attributes
屬性 描述
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 這個設定僅對多結果集的情況適用,它將列出語句執行後返回的結果集並每個結果集給一個名稱,名稱是逗號分隔的。

insert, update 和 delete

資料變更語句 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 's Attributes
屬性 描述
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 和 SQL Server 這樣的關係資料庫管理系統的自動遞增欄位),預設值:false。
keyProperty (僅對 insert 和 update 有用)唯一標記一個屬性,MyBatis 會通過 getGeneratedKeys 的返回值或者通過 insert 語句的 selectKey 子元素設定它的鍵值,預設:unset。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。
keyColumn (僅對 insert 和 update 有用)通過生成的鍵值設定表中的列名,這個設定僅在某些資料庫(像 PostgreSQL)是必須的,當主鍵列不是表中的第一列的時候需要設定。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。
databaseId 如果配置了 databaseIdProvider,MyBatis 會載入所有的不帶 databaseId 或匹配當前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。

下面就是 insert,update 和 delete 語句的示例:

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
  update Author set
    username = #{username},
    password = #{password},
    email = #{email},
    bio = #{bio}
  where id = #{id}
</update>

<delete id="deleteAuthor">
  delete from Author where id = #{id}
</delete>

如前所述,插入語句的配置規則更加豐富,在插入語句裡面有一些額外的屬性和子元素用來處理主鍵的生成,而且有多種生成方式。

首先,如果你的資料庫支援自動生成主鍵的欄位(比如 MySQL 和 SQL Server),那麼你可以設定 useGeneratedKeys=”true”,然後再把 keyProperty 設定到目標屬性上就OK了。例如,如果上面的 Author 表已經對 id 使用了自動生成的列型別,那麼語句可以修改為:

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username,password,email,bio)
  values (#{username},#{password},#{email},#{bio})
</insert>

如果你的資料庫還支援多行插入, 你也可以傳入一個Authors陣列或集合,並返回自動生成的主鍵。

<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 有另外一種方法來生成主鍵。

這裡有一個簡單(甚至很傻)的示例,它可以生成一個隨機 ID(你最好不要這麼做,但這裡展示了 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 程式碼變得複雜。

selectKey 元素描述如下:

<selectKey
  keyProperty="id"
  resultType="int"
  order="BEFORE"
  statementType="PREPARED">
selectKey Attributes
屬性 描述
keyProperty selectKey 語句結果應該被設定的目標屬性。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。
keyColumn 匹配屬性的返回結果集中的列名稱。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。
resultType 結果的型別。MyBatis 通常可以推算出來,但是為了更加確定寫上也不會有什麼問題。MyBatis 允許任何簡單型別用作主鍵的型別,包括字串。如果希望作用於多個生成的列,則可以使用一個包含期望屬性的 Object 或一個 Map。
order 這可以被設定為 BEFORE 或 AFTER。如果設定為 BEFORE,那麼它會首先選擇主鍵,設定 keyProperty 然後執行插入語句。如果設定為 AFTER,那麼先執行插入語句,然後是 selectKey 元素 - 這和像 Oracle 的資料庫相似,在插入語句內部可能有嵌入索引呼叫。
statementType 與前面相同,MyBatis 支援 STATEMENT,PREPARED 和 CALLABLE 語句的對映型別,分別代表 PreparedStatement 和 CallableStatement 型別。

sql

這個元素可以被用來定義可重用的 SQL 程式碼段,可以包含在其他語句中。它可以被靜態地(在載入引數) 引數化. 不同的屬性值通過包含的例項變化. 比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </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 some_table t1
    cross join some_table t2
</select>

屬性值也可以被用在 include 元素的 refid 屬性裡(

<include refid="${include_target}"/>

)或 include 內部語句中(

${prefix}Table

),例如:

<sql id="sometable">
  ${prefix}Table
</sql>

<sql id="someinclude">
  from
    <include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
  select
    field1, field2, field3
  <include refid="someinclude">
    <property name="prefix" value="Some"/>
    <property name="include_target" value="sometable"/>
  </include>
</select>

引數(Parameters)

前面的所有語句中你所見到的都是簡單引數的例子,實際上引數是 MyBatis 非常強大的元素,對於簡單的做法,大概 90% 的情況引數都很少,比如:

<select id="selectUsers" resultType="User">
  select id, username, password
  from users
  where id = #{id}
</select>

上面的這個示例說明了一個非常簡單的命名引數對映。引數型別被設定為 int,這樣這個引數就可以被設定成任何內容。原生的型別或簡單資料型別(比如整型和字串)因為沒有相關屬性,它會完全用引數值來替代。然而,如果傳入一個複雜的物件,行為就會有一點不同了。比如:

<insert id="insertUser" parameterType="User">
  insert into users (id, username, password)
  values (#{id}, #{username}, #{password})
</insert>

如果 User 型別的引數物件傳遞到了語句中,id、username 和 password 屬性將會被查詢,然後將它們的值傳入預處理語句的引數中。

這點相對於向語句中傳參是比較好的,而且又簡單,不過引數對映的功能遠不止於此。

首先,像 MyBatis 的其他部分一樣,引數也可以指定一個特殊的資料型別。

#{property,javaType=int,jdbcType=NUMERIC}

像 MyBatis 的剩餘部分一樣,javaType 通常可以由引數物件確定,除非該物件是一個 HashMap。這時所使用的 TypeHandler 應該明確指明 javaType。

NOTE 如果一個列允許 null 值,並且會傳遞值 null 的引數,就必須要指定 JDBC Type。閱讀 PreparedStatement.setNull() 的 JavaDocs 文件來獲取更多資訊。

為了以後定製型別處理方式,你也可以指定一個特殊的型別處理器類(或別名),比如:

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

儘管看起來配置變得越來越繁瑣,但實際上,很少需要去設定它們。

對於數值型別,還有一個小數保留位數的設定,來確定小數點後保留的位數。

#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}

最後,mode 屬性允許你指定 IN,OUT 或 INOUT 引數。如果引數為 OUT 或 INOUT,引數物件屬性的真實值將會被改變,就像你在獲取輸出引數時所期望的那樣。如果 mode 為 OUT(或 INOUT),而且 jdbcType 為 CURSOR(也就是 Oracle 的 REFCURSOR),你必須指定一個 resultMap 來對映結果集 ResultMap 到引數型別。要注意這裡的 javaType 屬性是可選的,如果留空並且 jdbcType 是 CURSOR,它會被自動地被設為 ResultMap。

#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}

MyBatis 也支援很多高階的資料型別,比如結構體,但是當註冊 out 引數時你必須告訴它語句型別名稱。比如(再次提示,在實際中要像這樣不能換行):

#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}

儘管所有這些選項很強大,但大多時候你只須簡單地指定屬性名,其他的事情 MyBatis 會自己去推斷,頂多要為可能為空的列指定 jdbcType。

#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}

字串替換

預設情況下,使用 #{} 格式的語法會導致 MyBatis 建立 PreparedStatement 引數並安全地設定引數(就像使用 ? 一樣)。這樣做更安全,更迅速,通常也是首選做法,不過有時你就是想直接在 SQL 語句中插入一個不轉義的字串。比如,像 ORDER BY,你可以這樣來使用:

ORDER BY ${columnName}

這裡 MyBatis 不會修改或轉義字串。

NOTE 用這種方式接受使用者的輸入,並將其用於語句中的引數是不安全的,會導致潛在的 SQL 注入攻擊,因此要麼不允許使用者輸入這些欄位,要麼自行轉義並檢驗。

Result Maps

resultMap 元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90% 的 JDBC ResultSets 資料提取程式碼中解放出來, 並在一些情形下允許你做一些 JDBC 不支援的事情。 實際上,在對複雜語句進行聯合對映的時候,它很可能可以代替數千行的同等功能的程式碼。 ResultMap 的設計思想是,簡單的語句不需要明確的結果對映,而複雜一點的語句只需要描述它們的關係就行了。

你已經見過簡單對映語句的示例了,但沒有明確的 resultMap。比如:

<select id="selectUsers" resultType="map">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

上述語句只是簡單地將所有的列對映到 HashMap 的鍵上,這由 resultType 屬性指定。 雖然在大部分情況下都夠用,但是 HashMap 不是一個很好的領域模型。 你的程式更可能會使用 JavaBean 或 POJO(Plain Old Java Objects,普通 Java 物件)作為領域模型。 MyBatis 對兩者都支援。看看下面這個 JavaBean:

package com.someapp.model;
public class User {
  private int id;
  private String username;
  private String hashedPassword;
  
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getUsername() {
    return username;
  }
  public void setUsername(String username) {
    this.username = username;
  }
  public String getHashedPassword() {
    return hashedPassword;
  }
  public void setHashedPassword(String hashedPassword) {
    this.hashedPassword = hashedPassword;
  }
}

基於 JavaBean 的規範,上面這個類有 3 個屬性:id,username 和 hashedPassword。這些屬性會對應到 select 語句中的列名。

這樣的一個 JavaBean 可以被對映到 ResultSet,就像對映到 HashMap 一樣簡單。

<select id="selectUsers" resultType="com.someapp.model.User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

類型別名是你的好幫手。使用它們,你就可以不用輸入類的完全限定名稱了。比如:

<!-- In mybatis-config.xml file -->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- In SQL Mapping XML file -->
<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

這些情況下,MyBatis 會在幕後自動建立一個 ResultMap,再基於屬性名來對映列到 JavaBean 的屬性上。如果列名和屬性名沒有精確匹配,可以在 SELECT 語句中對列使用別名(這是一個 基本的 SQL 特性)來匹配標籤。比如:

<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>

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>

引用它的語句使用 resultMap 屬性就行了(注意我們去掉了 resultType 屬性)。比如:

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>

如果世界總是這麼簡單就好了。

高階結果對映

MyBatis 建立的一個想法是:資料庫不可能永遠是你所想或所需的那個樣子。 我們希望每個資料庫都具備良好的第三正規化或 BCNF 正規化,可惜它們不總都是這樣。 如果有一個獨立且完美的資料庫對映模式,所有應用程式都可以使用它,那就太好了,但可惜也沒有。 ResultMap 就是 MyBatis 對這個問題的答案。

比如,我們如何對映下面這個語句?

<!-- Very Complex Statement -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
  select
       B.id as blog_id,
       B.title as blog_title,
       B.author_id as blog_author_id,
       A.id as author_id,
       A.username as author_username,
       A.password as author_password,
       A.email as author_email,
       A.bio as author_bio,
       A.favourite_section as author_favourite_section,
       P.id as post_id,
       P.blog_id as post_blog_id,
       P.author_id as post_author_id,
       P.created_on as post_created_on,
       P.section as post_section,
       P.subject as post_subject,
       P.draft as draft,
       P.body as post_body,
       C.id as comment_id,
       C.post_id as comment_post_id,
       C.name as comment_name,
       C.comment as comment_text,
       T.id as tag_id,
       T.name as tag_name
  from Blog B
       left outer join Author A on B.author_id = A.id
       left outer join Post P on B.id = P.blog_id
       left outer join Comment C on P.id = C.post_id
       left outer join Post_Tag PT on PT.post_id = P.id
       left outer join Tag T on PT.tag_id = T.id
  where B.id = #{id}
</select>

你可能想把它對映到一個智慧的物件模型,這個物件表示了一篇部落格,它由某位作者所寫, 有很多的博文,每篇博文有零或多條的評論和標籤。 我們來看看下面這個完整的例子,它是一個非常複雜的 ResultMap (假設作者,部落格,博文,評論和標籤都是型別的別名)。 不用緊張,我們會一步一步來說明。 雖然它看起來令人望而生畏,但其實非常簡單。

<!-- 超複雜的 Result Map -->
<resultMap id="detailedBlogResultMap" type="Blog">
  <constructor>
    <idArg column="blog_id" javaType="int"/>
  </constructor>
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
    <result property="favouriteSection" column="author_favourite_section"/>
  </association>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <association property="author" javaType="Author"/>
    <collection property="comments" ofType="Comment">
      <id property="id" column="comment_id"/>
    </collection>
    <collection property="tags" ofType="Tag" >
      <id property="id" column="tag_id"/>
    </collection>
    <discriminator javaType="int" column="draft">
      <case value="1" resultType="DraftPost"/>
    </discriminator>
  </collection>
</resultMap>

resultMap 元素有很多子元素和一個值得討論的結構。 下面是 resultMap 元素的概念檢視。

resultMap

  • constructor - 用於在例項化類時,注入結果到構造方法中
    • idArg - ID 引數;標記出作為 ID 的結果可以幫助提高整體效能
    • arg - 將被注入到構造方法的一個普通結果
  • id – 一個 ID 結果;標記出作為 ID 的結果可以幫助提高整體效能
  • result – 注入到欄位或 JavaBean 屬性的普通結果
  • association – 一個複雜型別的關聯;許多結果將包裝成這種型別
    • 巢狀結果對映 – 關聯可以指定為一個 resultMap 元素,或者引用一個
  • collection – 一個複雜型別的集合
    • 巢狀結果對映 – 集合可以指定為一個 resultMap 元素,或者引用一個
  • discriminator – 使用結果值來決定使用哪個 resultMap
    • case – 基於某些值的結果對映
      • 巢狀結果對映 – 一個 case 也是一個對映它本身的結果,因此可以包含很多相 同的元素,或者它可以參照一個外部的 resultMap。
ResultMap &#x7684;&#x5c5e;&#x6027;&#x5217;&#x8868;
屬性 描述
id 當前名稱空間中的一個唯一標識,用於標識一個result map.
type 類的完全限定名, 或者一個類型別名 (內建的別名可以參考上面的表格).
autoMapping 如果設定這個屬性,MyBatis將會為這個ResultMap開啟或者關閉自動對映。這個屬性會覆蓋全域性的屬性 autoMappingBehavior。預設值為:unset。

最佳實踐 最好一步步地建立結果對映。單元測試可以在這個過程中起到很大幫助。如果你嘗試一次建立一個像上面示例那樣的巨大的結果對映, 那麼很可能會出現錯誤而且很難去使用它來完成工作。 從最簡單的形態開始,逐步進化。而且別忘了單元測試!使用框架的缺點是有時候它們看上去像黑盒子(無論原始碼是否可見)。 為了確保你實現的行為和想要的一致,最好的選擇是編寫單元測試。提交 bug 的時候它也能起到很大的作用。

下一部分將詳細說明每個元素。

id & result

<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>

這些是結果對映最基本的內容。id 和 result 都將一個列的值對映到一個簡單資料型別(字串,整型,雙精度浮點數,日期等)的屬性或欄位。

這兩者之間的唯一不同是, id 表示的結果將是物件的標識屬性,這會在比較物件例項時用到。 這樣可以提高整體的效能,尤其是快取和巢狀結果對映(也就是聯合對映)的時候。

兩個元素都有一些屬性:

Id &#x548c; Result &#x7684;&#x5c5e;&#x6027;
屬性 描述
property 對映到列結果的欄位或屬性。如果用來匹配的 JavaBeans 存在給定名字的屬性,那麼它將會被使用。否則 MyBatis 將會尋找給定名稱 property 的欄位。 無論是哪一種情形,你都可以使用通常的點式分隔形式進行復雜屬性導航。比如,你可以這樣對映一些簡單的東西: “username” ,或者對映到一些複雜的東西: “address.street.number” 。
column 資料庫中的列名,或者是列的別名。一般情況下,這和 傳遞給 resultSet.getString(columnName) 方法的引數一樣。
javaType 一個 Java 類的完全限定名,或一個類型別名(參考上面內建類型別名 的列表) 。如果你對映到一個 JavaBean,MyBatis 通常可以斷定型別。 然而,如果你對映到的是 HashMap,那麼你應該明確地指定 javaType 來保證期望的行為。
jdbcType JDBC 型別,所支援的 JDBC 型別參見這個表格之後的“支援的 JDBC 型別”。 只需要在可能執行插入、更新和刪除的允許空值的列上指定 JDBC 型別。這是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 程式設計,你需要對可能為 null 的值指定這個型別。
typeHandler 我們在前面討論過的預設型別處理器。使用這個屬性,你可以覆蓋默 認的型別處理器。這個屬性值是一個型別處理 器實現類的完全限定名,或者是類型別名。

支援的 JDBC 型別

為了未來的參考,MyBatis 通過包含的 jdbcType 列舉型,支援下面的 JDBC 型別。

BIT FLOAT CHAR TIMESTAMP OTHER UNDEFINED
TINYINT REAL VARCHAR BINARY BLOB NVARCHAR
SMALLINT DOUBLE LONGVARCHAR VARBINARY CLOB NCHAR
INTEGER NUMERIC DATE LONGVARBINARY BOOLEAN NCLOB
BIGINT DECIMAL TIME NULL CURSOR ARRAY

構造方法

通過修改物件屬性的方式,可以滿足大多數的資料傳輸物件(Data Transfer Object,DTO)以及絕大部分領域模型的要求。 但有些情況下你想使用不可變類。 通常來說,很少或基本不變的、包含引用或查詢數 據的表,很適合使用不可變類。 構造方法注入允許你在初始化時 為類設定屬性的值,而不用暴露出公有方法。MyBatis 也支援私有屬性和私有 JavaBeans 屬 性來達到這個目的,但有一些人更青睞於構造方法注入。constructor 元素就是為此而生的。

看看下面這個構造方法:

public class User {
   //...
   public User(Integer id, String username, int age) {
     //...
  }
//...
}

為了將結果注入構造方法,MyBatis需要通過某種方式定位相應的構造方法。 在下面的例子中,MyBatis搜尋一個聲明瞭三個形參的的構造方法,以 java.lang.Integer, java.lang.Stringand int 的順序排列。

<constructor>
   <idArg column="id" javaType="int"/>
   <arg column="username" javaType="String"/>
   <arg column="age" javaType="_int"/>
</constructor>

當你在處理一個帶有多個形參的構造方法時,很容易在保證 arg 元素的正確順序上出錯。 從版本 3.4.3 開始,可以在指定引數名稱的前提下,以任意順序編寫 arg 元素。 為了通過名稱來引用構造方法引數,你可以新增 @Param 註解,或者使用 '-parameters' 編譯選項並啟用 useActualParamName 選項(預設開啟)來編譯專案。 下面的例子對於同一個構造方法依然是有效的,儘管第二和第三個形參順序與構造方法中宣告的順序不匹配。

<constructor>
   <idArg column="id" javaType="int" name="id" />
   <arg column="age" javaType="_int" name="age" />
   <arg column="username" javaType="String" name="username" />
</constructor>

如果類中存在名稱和型別相同的屬性,那麼可以省略 javaType 。

剩餘的屬性和規則和普通的 id 和 result 元素是一樣的。

屬性 描述
column 資料庫中的列名,或者是列的別名。一般情況下,這和 傳遞給 resultSet.getString(columnName) 方法的引數一樣。
javaType 一個 Java 類的完全限定名,或一個類型別名(參考上面內建類型別名的列表)。 如果你對映到一個 JavaBean,MyBatis 通常可以斷定型別。然而,如 果你對映到的是 HashMap,那麼你應該明確地指定 javaType 來保證期望的 行為。
jdbcType JDBC 型別,所支援的 JDBC 型別參見這個表格之前的“支援的 JDBC 型別”。 只需要在可能執行插入、更新和刪除的允許空值的列上指定 JDBC 型別。這是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 程式設計,你需要對可能為 null 的值指定這個型別。
typeHandler 我們在前面討論過的預設型別處理器。使用這個屬性,你可以覆蓋默 認的型別處理器。這個屬性值是一個型別處理 器實現類的完全限定名,或者是類型別名。
select 用於載入複雜型別屬性的對映語句的 ID,它會從 column 屬性中指定的列檢索資料,作為引數傳遞給此 select 語句。具體請參考 Association 標籤。
resultMap ResultMap 的 ID,可以將巢狀的結果集對映到一個合適的物件樹中,功能和 select 屬性相似,它可以實現將多表連線操作的結果對映成一個單一的ResultSet。這樣的ResultSet將會將包含重複或部分資料重複的結果集正確的對映到巢狀的物件樹中。為了實現它, MyBatis允許你 “串聯” ResultMap,以便解決巢狀結果集的問題。想了解更多內容,請參考下面的Association元素。
name 構造方法形參的名字。從3.4.3版本開始,通過指定具體的名字,你可以以任意順序寫入arg元素。參看上面的解釋。

關聯

<association property="author" column="blog_author_id" javaType="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
</association>

關聯元素處理“有一個”型別的關係。比如,在我們的示例中,一個部落格有一個使用者。 關聯對映就工作於這種結果之上。你指定了目標屬性,來獲取值的列,屬性的 java 型別(很 多情況下 MyBatis 可以自己算出來) ,如果需要的話還有 jdbc 型別,如果你想覆蓋或獲取的 結果值還需要型別控制器。

關聯中不同的是你需要告訴 MyBatis 如何載入關聯。MyBatis 在這方面會有兩種不同的 方式:

  • 巢狀查詢:通過執行另外一個 SQL 對映語句來返回預期的複雜型別。
  • 巢狀結果:使用巢狀結果對映來處理重複的聯合結果的子集。首先,然讓我們來檢視這個元素的屬性。所有的你都會看到,它和普通的只由 select 和

resultMap 屬性的結果對映不同。

屬性 描述
property 對映到列結果的欄位或屬性。如果用來匹配的 JavaBeans 存在給定名字的屬性,那麼它將會被使用。 否則 MyBatis 將會尋找與給定名稱相同的欄位。 這兩種情形你可以使用通常點式的複雜屬性導航。比如,你可以這樣對映 一 些 東 西 :“ username ”, 或 者 映 射 到 一 些 復 雜 的 東 西 : “address.street.number” 。
javaType 一個 Java 類的完全限定名,或一個類型別名(參考上面內建類型別名的列 表) 。如果你對映到一個 JavaBean,MyBatis 通常可以斷定型別。然而,如 javaType 果你對映到的是 HashMap,那麼你應該明確地指定 javaType 來保證所需的 行為。
jdbcType 在這個表格之前的所支援的 JDBC 型別列表中的型別。JDBC 型別是僅僅 需要對插入, 更新和刪除操作可能為空的列進行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 程式設計,你需要指定這個型別-但 僅僅對可能為空的值。
typeHandler 我們在前面討論過預設的型別處理器。使用這個屬性,你可以覆蓋預設的 typeHandler 型別處理器。 這個屬性值是類的完全限定名或者是一個型別處理器的實現, 或者是類型別名。

關聯的巢狀查詢

屬性 描述
column 來自資料庫的列名,或重新命名的列標籤。這和通常傳遞給 resultSet.getString(columnName)方法的字串是相同的。 column 注 意 : 要 處 理 復 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= ” {prop1=col1,prop2=col2} ” 這種語法來傳遞給巢狀查詢語 句。這會引起 prop1 和 prop2 以引數物件形式來設定給目標巢狀查詢語句。
select 另外一個對映語句的 ID,可以載入這個屬性對映需要的複雜型別。獲取的 在列屬性中指定的列的值將被傳遞給目標 select 語句作為引數。表格後面 有一個詳細的示例。 select 注 意 : 要 處 理 復 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= ” {prop1=col1,prop2=col2} ” 這種語法來傳遞給巢狀查詢語 句。這會引起 prop1 和 prop2 以引數物件形式來設定給目標巢狀查詢語句。
fetchType 可選的。有效值為 lazy和eager。 如果使用了,它將取代全域性配置引數lazyLoadingEnabled。

示例:

<resultMap id="blogResult" type="Blog">
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>

我們有兩個查詢語句:一個來載入部落格,另外一個來載入作者,而且部落格的結果對映描 述了“selectAuthor”語句應該被用來載入它的 author 屬性。

其他所有的屬性將會被自動載入,假設它們的列和屬性名相匹配。

這種方式很簡單, 但是對於大型資料集合和列表將不會表現很好。 問題就是我們熟知的 “N+1 查詢問題”。概括地講,N+1 查詢問題可以是這樣引起的:

  • 你執行了一個單獨的 SQL 語句來獲取結果列表(就是“+1”)。
  • 對返回的每條記錄,你執行了一個查詢語句來為每個載入細節(就是“N”)。

這個問題會導致成百上千的 SQL 語句被執行。這通常不是期望的。

MyBatis 能延遲載入這樣的查詢就是一個好處,因此你可以分散這些語句同時執行的消 耗。然而,如果你載入一個列表,之後迅速迭代來訪問巢狀的資料,你會呼叫所有的延遲加 載,這樣的行為可能是很糟糕的。

所以還有另外一種方法。

關聯的巢狀結果

屬性 描述
resultMap 這是結果對映的 ID,可以對映關聯的巢狀結果到一個合適的物件圖中。這 是一種替代方法來呼叫另外一個查詢語句。這允許你聯合多個表來合成到 resultMap 一個單獨的結果集。這樣的結果集可能包含重複,資料的重複組需要被分 解,合理對映到一個巢狀的物件圖。為了使它變得容易,MyBatis 讓你“鏈 接”結果對映,來處理巢狀結果。一個例子會很容易來仿照,這個表格後 面也有一個示例。
columnPrefix 當連線多表時,你將不得不使用列別名來避免ResultSet中的重複列名。指定columnPrefix允許你對映列名到一個外部的結果集中。 請看後面的例子。
notNullColumn 預設情況下,子物件僅在至少一個列對映到其屬性非空時才建立。 通過對這個屬性指定非空的列將改變預設行為,這樣做之後Mybatis將僅在這些列非空時才建立一個子物件。 可以指定多個列名,使用逗號分隔。預設值:未設定(unset)。
autoMapping 如果使用了,當對映結果到當前屬性時,Mybatis將啟用或者禁用自動對映。 該屬性覆蓋全域性的自動對映行為。 注意它對外部結果集無影響,所以在select or resultMap屬性中這個是毫無意義的。 預設值:未設定(unset)。

在上面你已經看到了一個非常複雜的巢狀關聯的示例。 下面這個是一個非常簡單的示例 來說明它如何工作。代替了執行一個分離的語句,我們聯合部落格表和作者表在一起,就像:

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    B.author_id     as blog_author_id,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>

注意這個聯合查詢, 以及採取保護來確保所有結果被唯一而且清晰的名字來重新命名。 這使得對映非常簡單。現在我們可以對映這個結果:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>

在上面的示例中你可以看到部落格的作者關聯代表著“authorResult”結果對映來載入作 者例項。

非常重要: id元素在巢狀結果對映中扮演著非 常重要的角色。你應該總是指定一個或多個可以唯一標識結果的屬性。實際上如果你不指定它的話, MyBatis仍然可以工作,但是會有嚴重的效能問題。在可以唯一標識結果的情況下, 儘可能少的選擇屬性。主鍵是一個顯而易見的選擇(即使是複合主鍵)。

現在,上面的示例用了外部的結果對映元素來對映關聯。這使得 Author 結果對映可以 重用。然而,如果你不需要重用它的話,或者你僅僅引用你所有的結果對映合到一個單獨描 述的結果對映中。你可以巢狀結果對映。這裡給出使用這種方式的相同示例:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
  </association>
</resultMap>

如果blog有一個co-author怎麼辦? select語句將看起來這個樣子:

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio,
    CA.id           as co_author_id,
    CA.username     as co_author_username,
    CA.password     as co_author_password,
    CA.email        as co_author_email,
    CA.bio          as co_author_bio
  from Blog B
  left outer join Author A on B.author_id = A.id
  left outer join Author CA on B.co_author_id = CA.id
  where B.id = #{id}
</select>

再次呼叫Author的resultMap將定義如下:

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>

因為結果中的列名與resultMap中的列名不同。 你需要指定columnPrefix去重用對映co-author結果的resultMap。

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author"
    resultMap="authorResult" />
  <association property="coAuthor"
    resultMap="authorResult"
    columnPrefix="co_" />
</resultMap>

上面你已經看到了如何處理“有一個”型別關聯。但是“有很多個”是怎樣的?下面這 個部分就是來討論這個主題的。

集合

<collection property="posts" ofType="domain.blog.Post">
  <id property="id" column="post_id"/>
  <result property="subject" column="post_subject"/>
  <result property="body" column="post_body"/>
</collection>

集合元素的作用幾乎和關聯是相同的。實際上,它們也很相似,文件的異同是多餘的。 所以我們更多關注於它們的不同。

我們來繼續上面的示例,一個部落格只有一個作者。但是部落格有很多文章。在部落格類中, 這可以由下面這樣的寫法來表示:

private List<Post> posts;

要對映巢狀結果集合到 List 中,我們使用集合元素。就像關聯元素一樣,我們可以從 連線中使用巢狀查詢,或者巢狀結果。

集合的巢狀查詢

首先,讓我們看看使用巢狀查詢來為部落格載入文章。

<resultMap id="blogResult" type="Blog">
  <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectPostsForBlog" resultType="Post">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

這裡你應該注意很多東西,但大部分程式碼和上面的關聯元素是非常相似的。首先,你應 該注意我們使用的是集合元素。然後要注意那個新的“ofType”屬性。這個屬性用來區分 JavaBean(或欄位)屬性型別和集合包含的型別來說是很重要的。所以你可以讀出下面這個 對映:

<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>

讀作: “在 Post 型別的 ArrayList 中的 posts 的集合。”

javaType 屬性是不需要的,因為 MyBatis 在很多情況下會為你算出來。所以你可以縮短 寫法:

<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>

集合的巢狀結果

至此,你可以猜測集合的巢狀結果是如何來工作的,因為它和關聯完全相同,除了它應 用了一個“ofType”屬性

首先, 讓我們看看 SQL:

<select id="selectBlog" resultMap="blogResult">
  select
  B.id as blog_id,
  B.title as blog_title,
  B.author_id as blog_author_id,
  P.id as post_id,
  P.subject as post_subject,
  P.body as post_body,
  from Blog B
  left outer join Post P on B.id = P.blog_id
  where B.id = #{id}
</select>

我們又一次聯合了部落格表和文章表,而且關注於保證特性,結果列標籤的簡單對映。現 在用文章對映集合對映部落格,可以簡單寫為:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
  </collection>
</resultMap>

同樣,要記得 id 元素的重要性,如果你不記得了,請閱讀上面的關聯部分。

同樣, 如果你引用更長的形式允許你的結果對映的更多重用, 你可以使用下面這個替代 的對映:

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
  <id property="id" column="id"/>
  <result property="subject" column="subject"/>
  <result property="body" column="body"/>
</resultMap>

注意 這個對你所對映的內容沒有深度,廣度或關聯和集合相聯合的限制。當對映它們 時你應該在大腦中保留它們的表現。 你的應用在找到最佳方法前要一直進行的單元測試和性 能測試。好在 myBatis 讓你後來可以改變想法,而不對你的程式碼造成很小(或任何)影響。

高階關聯和集合對映是一個深度的主題。文件只能給你介紹到這了。加上一點聯絡,你 會很快清楚它們的用法。

鑑別器

<discriminator javaType="int" column="draft">
  <case value="1" resultType="DraftPost"/>
</discriminator>

有時一個單獨的資料庫查詢也許返回很多不同 (但是希望有些關聯) 資料型別的結果集。 鑑別器元素就是被設計來處理這個情況的, 還有包括類的繼承層次結構。 鑑別器非常容易理 解,因為它的表現很像 Java 語言中的 switch 語句。

定義鑑別器指定了 column 和 javaType 屬性。 列是 MyBatis 查詢比較值的地方。 JavaType 是需要被用來保證等價測試的合適型別(儘管字串在很多情形下都會有用)。比如:

<resultMap id="vehicleResult" type="Vehicle">
  <id property="id" column="id" />
  <result property="vin" column="vin"/>
  <result property="year" column="year"/>
  <result property="make" column="make"/>
  <result property="model" column="model"/>
  <result property="color" column="color"/>
  <discriminator javaType="int" column="vehicle_type">
    <case value="1" resultMap="carResult"/>
    <case value="2" resultMap="truckResult"/>
    <case value="3" resultMap="vanResult"/>
    <case value="4" resultMap="suvResult"/>
  </discriminator>
</resultMap>

在這個示例中, MyBatis 會從結果集中得到每條記錄, 然後比較它的 vehicle 型別的值。 如果它匹配任何一個鑑別器的例項,那麼就使用這個例項指定的結果對映。換句話說,這樣 做完全是剩餘的結果對映被忽略(除非它被擴充套件,這在第二個示例中討論) 。如果沒有任何 一個例項相匹配,那麼 MyBatis 僅僅使用鑑別器塊外定義的結果對映。所以,如果 carResult 按如下宣告:

<resultMap id="carResult" type="Car">
  <result property="doorCount" column="door_count" />
</resultMap>

那麼只有 doorCount 屬性會被載入。這步完成後完整地允許鑑別器例項的獨立組,儘管 和父結果對映可能沒有什麼關係。這種情況下,我們當然知道 cars 和 vehicles 之間有關係, 如 Car 是一個 Vehicle 例項。因此,我們想要剩餘的屬性也被載入。我們設定的結果對映的 簡單改變如下。

<resultMap id="carResult" type="Car" extends="vehicleResult">
  <result property="doorCount" column="door_count" />
</resultMap>

現在 vehicleResult 和 carResult 的屬性都會被載入了。

儘管曾經有些人會發現這個外部對映定義會多少有一些令人厭煩之處。 因此還有另外一 種語法來做簡潔的對映風格。比如:

<resultMap id="vehicleResult" type="Vehicle">
  <id property="id" column="id" />
  <result property="vin" column="vin"/>
  <result property="year" column="year"/>
  <result property="make" column="make"/>
  <result property="model" column="model"/>
  <result property="color" column="color"/>
  <discriminator javaType="int" column="vehicle_type">
    <case value="1" resultType="carResult">
      <result property="doorCount" column="door_count" />
    </case>
    <case value="2" resultType="truckResult">
      <result property="boxSize" column="box_size" />
      <result property="extendedCab" column="extended_cab" />
    </case>
    <case value="3" resultType="vanResult">
      <result property="powerSlidingDoor" column="power_sliding_door" />
    </case>
    <case value="4" resultType="suvResult">
      <result property="allWheelDrive" column="all_wheel_drive" />
    </case>
  </discriminator>
</resultMap>

要記得 這些都是結果對映, 如果你不指定任何結果, 那麼 MyBatis 將會為你自動匹配列 和屬性。所以這些例子中的大部分是很冗長的,而其實是不需要的。也就是說,很多資料庫 是很複雜的,我們不太可能對所有示例都能依靠它。

自動對映

正如你在前面一節看到的,在簡單的場景下,MyBatis可以替你自動對映查詢結果。 如果遇到複雜的場景,你需要構建一個result map。 但是在本節你將看到,你也可以混合使用這兩種策略。 讓我們到深一點的層面上看看自動對映是怎樣工作的。

當自動對映查詢結果時,MyBatis會獲取sql返回的列名並在java類中查詢相同名字的屬性(忽略大小寫)。 這意味著如果Mybatis發現了ID列和id屬性,Mybatis會將ID的值賦給id

通常資料庫列使用大寫單詞命名,單詞間用下劃線分隔;而java屬性一般遵循駝峰命名法。 為了在這兩種命名方式之間啟用自動對映,需要將 mapUnderscoreToCamelCase設定為true。

自動對映甚至在特定的result map下也能工作。在這種情況下,對於每一個result map,所有的ResultSet提供的列, 如果沒有被手工對映,則將被自動對映。自動對映處理完畢後手工對映才會被處理。 在接下來的例子中, id 和 userName列將被自動對映, hashed_password 列將根據配置對映。

<select id="selectUsers" resultMap="userResultMap">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password
  from some_table
  where id = #{id}
</select>
<resultMap id="userResultMap" type="User">
  <result property="password" column="hashed_password"/>
</resultMap>

有三種自動對映等級:

  • NONE - 禁用自動對映。僅設定手動對映屬性。
  • PARTIAL - 將自動對映結果除了那些有內部定義內嵌結果對映的(joins).
  • FULL - 自動對映所有。

預設值是PARTIAL,這是有原因的。當使用FULL時,自動對映會在處理join結果時執行,並且join取得若干相同行的不同實體資料,因此這可能導致非預期的對映。下面的例子將展示這種風險:

<select id="selectBlog" resultMap="blogResult">
  select
    B.id,
    B.title,
    A.username,
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>
<resultMap id="blogResult" type="Blog">
  <association property="author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
  <result property="username" column="author_username"/>
</resultMap>

在結果中BlogAuthor均將自動對映。但是注意Author有一個id屬性,在ResultSet中有一個列名為id, 所以Author的id將被填充為Blog的id,這不是你所期待的。所以需要謹慎使用FULL。

通過新增autoMapping屬性可以忽略自動對映等級配置,你可以啟用或者禁用自動對映指定的ResultMap。

<resultMap id="userResultMap" type="User" autoMapping="false">
  <result property="password" column="hashed_password"/>
</resultMap>

快取

MyBatis 包含一個非常強大的查詢快取特性,它可以非常方便地配置和定製。MyBatis 3 中的快取實現的很多改進都已經實現了,使得它更加強大而且易於配置。

預設情況下是沒有開啟快取的,除了區域性的 session 快取,可以增強變現而且處理迴圈 依賴也是必須的。要開啟二級快取,你需要在你的 SQL 對映檔案中新增一行:

<cache/>

字面上看就是這樣。這個簡單語句的效果如下:

  • 對映語句檔案中的所有 select 語句將會被快取。
  • 對映語句檔案中的所有 insert,update 和 delete 語句會重新整理快取。
  • 快取會使用 Least Recently Used(LRU,最近最少使用的)演算法來收回。
  • 根據時間表(比如 no Flush Interval,沒有重新整理間隔), 快取不會以任何時間順序 來重新整理。
  • 快取會儲存列表集合或物件(無論查詢方法返回什麼)的 1024 個引用。
  • 快取會被視為是 read/write(可讀/可寫)的快取,意味著物件檢索不是共享的,而 且可以安全地被呼叫者修改,而不干擾其他呼叫者或執行緒所做的潛在修改。

NOTE The cache will only apply to statements declared in the mapping file where the cache tag is located. If you are using the Java API in conjunction with the XML mapping files, then statements declared in the companion interface will not be cached by default. You will need to refer to the cache region using the @CacheNamespaceRef annotation.

所有的這些屬性都可以通過快取元素的屬性來修改。比如:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

這個更高階的配置建立了一個 FIFO 快取,並每隔 60 秒重新整理,存數結果物件或列表的 512 個引用,而且返回的物件被認為是隻讀的,因此在不同執行緒中的呼叫者之間修改它們會 導致衝突。

可用的收回策略有:

  • LRU – 最近最少使用的:移除最長時間不被使用的物件。
  • FIFO – 先進先出:按物件進入快取的順序來移除它們。
  • SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的物件。
  • WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的物件。

預設的是 LRU。

flushInterval(重