鍍金池/ 教程/ Java/ Mapper XML 文件
Java API
SQL語句構(gòu)建器
Mapper XML 文件
XML 映射配置文件
mybatis – MyBatis 3 | 入門
Logging
動態(tài) SQL

Mapper XML 文件

MyBatis 的真正強(qiáng)大在于它的映射語句,也是它的魔力所在。由于它的異常強(qiáng)大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進(jìn)行對比,你會立即發(fā)現(xiàn)省掉了將近 95% 的代碼。MyBatis 就是針對 SQL 構(gòu)建的,并且比普通的方法做的更好。

SQL 映射文件有很少的幾個頂級元素(按照它們應(yīng)該被定義的順序):

  • cache – 給定命名空間的緩存配置。
  • cache-ref – 其他命名空間緩存配置的引用。
  • resultMap – 是最復(fù)雜也是最強(qiáng)大的元素,用來描述如何從數(shù)據(jù)庫結(jié)果集中來加載對象。
  • sql – 可被其他語句引用的可重用語句塊。
  • insert – 映射插入語句
  • update – 映射更新語句
  • delete – 映射刪除語句
  • select – 映射查詢語句

下一部分將從語句本身開始來描述每個元素的細(xì)節(jié)。

select

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

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

這個語句被稱作 selectPerson,接受一個 int(或 Integer)類型的參數(shù),并返回一個 HashMap 類型的對象,其中的鍵是列名,值便是結(jié)果行中的對應(yīng)值。

注意參數(shù)符號:

#{id}

這就告訴 MyBatis 創(chuàng)建一個預(yù)處理語句參數(shù),通過 JDBC,這樣的一個參數(shù)在 SQL 中會由一個"?"來標(biāo)識,并被傳遞到一個新的預(yù)處理語句中,就像這樣:

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

當(dāng)然,這需要很多單獨(dú)的 JDBC 的代碼來提取結(jié)果并將它們映射到對象實(shí)例中,這就是 MyBatis 節(jié)省你時間的地方。我們需要深入了解參數(shù)和結(jié)果映射,細(xì)節(jié)部分我們下面來了解。

select 元素有很多屬性允許你配置,來決定每條語句的作用細(xì)節(jié)。

 <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 在命名空間中唯一的標(biāo)識符,可以被用來引用這條語句。
parameterType 將會傳入這條語句的參數(shù)類的完全限定名或別名。這個屬性是可選的,因?yàn)?MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的參數(shù),默認(rèn)值為 unset。
resultType 從這條語句中返回的期望類型的類的完全限定名或別名。注意如果是集合情形,那應(yīng)該是集合可以包含的類型,而不能是集合本身。使用 resultType 或 resultMap,但不能同時使用。
resultMap 外部 resultMap 的命名引用。結(jié)果集的映射是 MyBatis 最強(qiáng)大的特性,對其有一個很好的理解的話,許多復(fù)雜映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同時使用。
flushCache 將其設(shè)置為 true,任何時候只要語句被調(diào)用,都會導(dǎo)致本地緩存和二級緩存都會被清空,默認(rèn)值:false。
useCache 將其設(shè)置為 true,將會導(dǎo)致本條語句的結(jié)果被二級緩存,默認(rèn)值:對 select 元素為 true。
timeout 這個設(shè)置是在拋出異常之前,驅(qū)動程序等待數(shù)據(jù)庫返回請求結(jié)果的秒數(shù)。默認(rèn)值為 unset(依賴驅(qū)動)。
fetchSize 這是嘗試影響驅(qū)動程序每次批量返回的結(jié)果行數(shù)和這個設(shè)置值相等。默認(rèn)值為 unset(依賴驅(qū)動)。
statementType STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認(rèn)值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一個,默認(rèn)值為 unset (依賴驅(qū)動)。
databaseId 如果配置了 databaseIdProvider,MyBatis 會加載所有的不帶 databaseId 或匹配當(dāng)前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。
resultOrdered 這個設(shè)置僅針對嵌套結(jié)果 select 語句適用:如果為 true,就是假設(shè)包含了嵌套結(jié)果集或是分組了,這樣的話當(dāng)返回一個主結(jié)果行的時候,就不會發(fā)生有對前面結(jié)果集的引用的情況。這就使得在獲取嵌套的結(jié)果集的時候不至于導(dǎo)致內(nèi)存不夠用。默認(rèn)值:false。
resultSets 這個設(shè)置僅對多結(jié)果集的情況適用,它將列出語句執(zhí)行后返回的結(jié)果集并每個結(jié)果集給一個名稱,名稱是逗號分隔的。

insert, update 和 delete

數(shù)據(jù)變更語句 insert,update 和 delete 的實(shí)現(xiàn)非常接近:

<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 的属性

屬性 描述
id 命名空間中的唯一標(biāo)識符,可被用來代表這條語句。
parameterType 將要傳入語句的參數(shù)的完全限定類名或別名。這個屬性是可選的,因?yàn)?MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的參數(shù),默認(rèn)值為 unset。
flushCache 將其設(shè)置為 true,任何時候只要語句被調(diào)用,都會導(dǎo)致本地緩存和二級緩存都會被清空,默認(rèn)值:true(對應(yīng)插入、更新和刪除語句)。
timeout 這個設(shè)置是在拋出異常之前,驅(qū)動程序等待數(shù)據(jù)庫返回請求結(jié)果的秒數(shù)。默認(rèn)值為 unset(依賴驅(qū)動)。
statementType STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認(rèn)值:PREPARED。
useGeneratedKeys (僅對 insert 和 update 有用)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數(shù)據(jù)庫內(nèi)部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關(guān)系數(shù)據(jù)庫管理系統(tǒng)的自動遞增字段),默認(rèn)值:false。
keyProperty (僅對 insert 和 update 有用)唯一標(biāo)記一個屬性,MyBatis 會通過 getGeneratedKeys 的返回值或者通過 insert 語句的 selectKey 子元素設(shè)置它的鍵值,默認(rèn):unset。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。
keyColumn (僅對 insert 和 update 有用)通過生成的鍵值設(shè)置表中的列名,這個設(shè)置僅在某些數(shù)據(jù)庫(像 PostgreSQL)是必須的,當(dāng)主鍵列不是表中的第一列的時候需要設(shè)置。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。
databaseId 如果配置了 databaseIdProvider,MyBatis 會加載所有的不帶 databaseId 或匹配當(dāng)前 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>

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

首先,如果你的數(shù)據(jù)庫支持自動生成主鍵的字段(比如 MySQL 和 SQL Server),那么你可以設(shè)置 useGeneratedKeys=”true”,然后再把 keyProperty 設(shè)置到目標(biāo)屬性上就OK了。例如,如果上面的 Author 表已經(jīng)對 id 使用了自動生成的列類型,那么語句可以修改為:

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

對于不支持自動生成類型的數(shù)據(jù)庫或可能不支持自動生成主鍵 JDBC 驅(qū)動來說,MyBatis 有另外一種方法來生成主鍵。

這里有一個簡單(甚至很傻)的示例,它可以生成一個隨機(jī) ID(你最好不要這么做,但這里展示了 MyBatis 處理問題的靈活性及其所關(guān)心的廣度):

<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 元素將會首先運(yùn)行,Author 的 id 會被設(shè)置,然后插入語句會被調(diào)用。這給你了一個和數(shù)據(jù)庫中來處理自動生成的主鍵類似的行為,避免了使 Java 代碼變得復(fù)雜。

selectKey 元素描述如下:

<selectKey
  keyProperty="id"
  resultType="int"
  order="BEFORE"
  statementType="PREPARED">

selectKey 的属性

屬性 描述
keyProperty selectKey 語句結(jié)果應(yīng)該被設(shè)置的目標(biāo)屬性。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。
keyColumn 匹配屬性的返回結(jié)果集中的列名稱。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。
resultType 結(jié)果的類型。MyBatis 通??梢酝扑愠鰜恚菫榱烁哟_定寫上也不會有什么問題。MyBatis 允許任何簡單類型用作主鍵的類型,包括字符串。如果希望作用于多個生成的列,則可以使用一個包含期望屬性的 Object 或一個 Map。
order 這可以被設(shè)置為 BEFORE 或 AFTER。如果設(shè)置為 BEFORE,那么它會首先選擇主鍵,設(shè)置 keyProperty 然后執(zhí)行插入語句。如果設(shè)置為 AFTER,那么先執(zhí)行插入語句,然后是 selectKey 元素 - 這和像 Oracle 的數(shù)據(jù)庫相似,在插入語句內(nèi)部可能有嵌入索引調(diào)用。
statementType 與前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 語句的映射類型,分別代表 PreparedStatement 和 CallableStatement 類型。

sql

這個元素可以被用來定義可重用的 SQL 代碼段,可以包含在其他語句中。It can be statically (during load phase) parametrized. Different property values can vary in include instances. 比如:

<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>

Property value can be also used in include refid attribute or property values inside include clause, for example:

<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>

參數(shù)(Parameters)

前面的所有語句中你所見到的都是簡單參數(shù)的例子,實(shí)際上參數(shù)是 MyBatis 非常強(qiáng)大的元素,對于簡單的做法,大概 90% 的情況參數(shù)都很少,比如:

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

上面的這個示例說明了一個非常簡單的命名參數(shù)映射。參數(shù)類型被設(shè)置為 int,這樣這個參數(shù)就可以被設(shè)置成任何內(nèi)容。原生的類型或簡單數(shù)據(jù)類型(比如整型和字符串)因?yàn)闆]有相關(guān)屬性,它會完全用參數(shù)值來替代。然而,如果傳入一個復(fù)雜的對象,行為就會有一點(diǎn)不同了。比如:

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

如果 User 類型的參數(shù)對象傳遞到了語句中,id、username 和 password 屬性將會被查找,然后將它們的值傳入預(yù)處理語句的參數(shù)中。

這點(diǎn)對于向語句中傳參是比較好的而且又簡單,不過參數(shù)映射的功能遠(yuǎn)不止于此。

首先,像 MyBatis 的其他部分一樣,參數(shù)也可以指定一個特殊的數(shù)據(jù)類型。

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

像 MyBatis 的剩余部分一樣,javaType 通??梢詮膮?shù)對象中來去確定,前提是只要對象不是一個 HashMap。那么 javaType 應(yīng)該被確定來保證使用正確類型處理器。

NOTE 如果 null 被當(dāng)作值來傳遞,對于所有可能為空的列,JDBC Type 是需要的。你可以自己通過閱讀預(yù)處理語句的 setNull() 方法的 JavaDocs 文檔來研究這種情況。

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

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

盡管看起來配置變得越來越繁瑣,但實(shí)際上是很少去設(shè)置它們。

對于數(shù)值類型,還有一個小數(shù)保留位數(shù)的設(shè)置,來確定小數(shù)點(diǎn)后保留的位數(shù)。

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

最后,mode 屬性允許你指定 IN,OUT 或 INOUT 參數(shù)。如果參數(shù)為 OUT 或 INOUT,參數(shù)對象屬性的真實(shí)值將會被改變,就像你在獲取輸出參數(shù)時所期望的那樣。如果 mode 為 OUT(或 INOUT),而且 jdbcType 為 CURSOR(也就是 Oracle 的 REFCURSOR),你必須指定一個 resultMap 來映射結(jié)果集到參數(shù)類型。要注意這里的 javaType 屬性是可選的,如果左邊的空白是 jdbcType 的 CURSOR 類型,它會自動地被設(shè)置為結(jié)果集。

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

MyBatis 也支持很多高級的數(shù)據(jù)類型,比如結(jié)構(gòu)體,但是當(dāng)注冊 out 參數(shù)時你必須告訴它語句類型名稱。比如(再次提示,在實(shí)際中要像這樣不能換行):

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

盡管所有這些強(qiáng)大的選項(xiàng)很多時候你只簡單指定屬性名,其他的事情 MyBatis 會自己去推斷,最多你需要為可能為空的列名指定 jdbcType

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

字符串替換

默認(rèn)情況下,使用#{}格式的語法會導(dǎo)致 MyBatis 創(chuàng)建預(yù)處理語句屬性并安全地設(shè)置值(比如?)。這樣做更安全,更迅速,通常也是首選做法,不過有時你只是想直接在 SQL 語句中插入一個不改變的字符串。比如,像 ORDER BY,你可以這樣來使用:

ORDER BY ${columnName}

這里 MyBatis 不會修改或轉(zhuǎn)義字符串。

NOTE 以這種方式接受從用戶輸出的內(nèi)容并提供給語句中不變的字符串是不安全的,會導(dǎo)致潛在的 SQL 注入攻擊,因此要么不允許用戶輸入這些字段,要么自行轉(zhuǎn)義并檢驗(yàn)。

Result Maps

resultMap 元素是 MyBatis 中最重要最強(qiáng)大的元素。它就是讓你遠(yuǎn)離 90%的需要從結(jié)果 集中取出數(shù)據(jù)的 JDBC 代碼的那個東西, 而且在一些情形下允許你做一些 JDBC 不支持的事 情。 事實(shí)上, 編寫相似于對復(fù)雜語句聯(lián)合映射這些等同的代碼, 也許可以跨過上千行的代碼。 ResultMap 的設(shè)計就是簡單語句不需要明確的結(jié)果映射,而很多復(fù)雜語句確實(shí)需要描述它們 的關(guān)系。

你已經(jīng)看到簡單映射語句的示例了,但沒有明確的 resultMap。比如:

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

這樣一個語句簡單作用于所有列被自動映射到 HashMap 的鍵上,這由 resultType 屬性 指定。這在很多情況下是有用的,但是 HashMap 不能很好描述一個領(lǐng)域模型。那樣你的應(yīng) 用程序?qū)褂?JavaBeans 或 POJOs(Plain Old Java Objects,普通 Java 對象)來作為領(lǐng)域 模型。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 的規(guī)范,上面這個類有 3 個屬性:id,username 和 hashedPassword。這些 在 select 語句中會精確匹配到列名。

這樣的一個 JavaBean 可以被映射到結(jié)果集,就像映射到 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 會在幕后自動創(chuàng)建一個 ResultMap,基于屬性名來映射列到 JavaBean 的屬性上。如果列名沒有精確匹配,你可以在列名上使用 select 字句的別名(一個 基本的 SQL 特性)來匹配標(biāo)簽。比如:

<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 最優(yōu)秀的地方你已經(jīng)了解了很多了,但是你還沒有真正的看到一個。這些簡 單的示例不需要比你看到的更多東西。 只是出于示例的原因, 讓我們來看看最后一個示例中 外部的 resultMap 是什么樣子的,這也是解決列名不匹配的另外一種方式。

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="username"/>
  <result property="password" column="password"/>
</resultMap>    

引用它的語句使用 resultMap 屬性就行了(注意我們?nèi)サ袅?resultType 屬性)。比如:

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

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

高級結(jié)果映射

MyBatis 創(chuàng)建的一個想法:數(shù)據(jù)庫不用永遠(yuǎn)是你想要的或需要它們是什么樣的。而我們 最喜歡的數(shù)據(jù)庫最好是第三范式或 BCNF 模式,但它們有時不是。如果可能有一個單獨(dú)的 數(shù)據(jù)庫映射,所有應(yīng)用程序都可以使用它,這是非常好的,但有時也不是。結(jié)果映射就是 MyBatis 提供處理這個問題的答案。

比如,我們?nèi)绾斡成湎旅孢@個語句?

<!-- 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>

你可能想把它映射到一個智能的對象模型,包含一個作者寫的博客,有很多的博文,每 篇博文有零條或多條的評論和標(biāo)簽。 下面是一個完整的復(fù)雜結(jié)果映射例子 (假設(shè)作者, 博客, 博文, 評論和標(biāo)簽都是類型的別名) 我們來看看, 。 但是不用緊張, 我們會一步一步來說明。 當(dāng)天最初它看起來令人生畏,但實(shí)際上非常簡單。

<!-- Very Complex 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 元素有很多子元素和一個值得討論的結(jié)構(gòu)。 下面是 resultMap 元素的概念視圖

resultMap

  • constructor - 類在實(shí)例化時,用來注入結(jié)果到構(gòu)造方法中
    • idArg - ID 參數(shù);標(biāo)記結(jié)果作為 ID 可以幫助提高整體效能
    • arg - 注入到構(gòu)造方法的一個普通結(jié)果
  • id – 一個 ID 結(jié)果;標(biāo)記結(jié)果作為 ID 可以幫助提高整體效能
  • result – 注入到字段或 JavaBean 屬性的普通結(jié)果
  • association – 一個復(fù)雜的類型關(guān)聯(lián);許多結(jié)果將包成這種類型
    • 嵌入結(jié)果映射 – 結(jié)果映射自身的關(guān)聯(lián),或者參考一個
  • collection – 復(fù)雜類型的集
    • 嵌入結(jié)果映射 – 結(jié)果映射自身的集,或者參考一個
  • discriminator – 使用結(jié)果值來決定使用哪個結(jié)果映射
    • case – 基于某些值的結(jié)果映射
      • 嵌入結(jié)果映射 – 這種情形結(jié)果也映射它本身,因此可以包含很多相 同的元素,或者它可以參照一個外部的結(jié)果映射。

ResultMap Attributes

Attribute Description
id A unique identifier in this namespace that can be used to reference this result map.
type A fully qualified Java class name, or a type alias (see the table above for the list of built-in type aliases).
autoMapping If present, MyBatis will enable or disable the automapping for this ResultMap. This attribute overrides the global autoMappingBehavior. Default: unset.

最佳實(shí)踐 通常逐步建立結(jié)果映射。單元測試的真正幫助在這里。如果你嘗試創(chuàng)建 一次創(chuàng)建一個向上面示例那樣的巨大的結(jié)果映射, 那么可能會有錯誤而且很難去控制它 來工作。開始簡單一些,一步一步的發(fā)展。而且要進(jìn)行單元測試!使用該框架的缺點(diǎn)是 它們有時是黑盒(是否可見源代碼) 。你確定你實(shí)現(xiàn)想要的行為的最好選擇是編寫單元 測試。它也可以你幫助得到提交時的錯誤。

下面一部分將詳細(xì)說明每個元素。

id & result

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

這些是結(jié)果映射最基本內(nèi)容。id 和 result 都映射一個單獨(dú)列的值到簡單數(shù)據(jù)類型(字符 串,整型,雙精度浮點(diǎn)數(shù),日期等)的單獨(dú)屬性或字段。

這兩者之間的唯一不同是 id 表示的結(jié)果將是當(dāng)比較對象實(shí)例時用到的標(biāo)識屬性。這幫 助來改進(jìn)整體表現(xiàn),特別是緩存和嵌入結(jié)果映射(也就是聯(lián)合映射) 。

每個都有一些屬性:

Id and Result Attributes

屬性 描述
property 映射到列結(jié)果的字段或?qū)傩浴H绻ヅ涞氖谴嬖诘?和給定名稱相同 的 JavaBeans 的屬性,那么就會使用。否則 MyBatis 將會尋找給定名稱 property 的字段。這兩種情形你可以使用通常點(diǎn)式的復(fù)雜屬性導(dǎo)航。比如,你 可以這樣映射一些東西: "username" ,或者映射到一些復(fù)雜的東西: "address.street.number" 。
column 從數(shù)據(jù)庫中得到的列名,或者是列名的重命名標(biāo)簽。這也是通常和會 傳遞給 resultSet.getString(columnName)方法參數(shù)中相同的字符串。
javaType 一個 Java 類的完全限定名,或一個類型別名(參考上面內(nèi)建類型別名 的列表) 。如果你映射到一個 JavaBean,MyBatis 通??梢詳喽愋?。 然而,如果你映射到的是 HashMap,那么你應(yīng)該明確地指定 javaType 來保證所需的行為。
jdbcType 在這個表格之后的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅 僅需要對插入,更新和刪除操作可能為空的列進(jìn)行處理。這是 JDBC jdbcType 的需要,而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定 這個類型-但僅僅對可能為空的值。
typeHandler 我們在前面討論過默認(rèn)的類型處理器。使用這個屬性,你可以覆蓋默 認(rèn)的類型處理器。這個屬性值是類的完全限定名或者是一個類型處理 器的實(shí)現(xiàn),或者是類型別名。

支持的 JDBC 類型

為了未來的參考,MyBatis 通過包含的 jdbcType 枚舉型,支持下面的 JDBC 類型。

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

構(gòu)造方法

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

對于大多數(shù)數(shù)據(jù)傳輸對象(Data Transfer Object,DTO)類型,屬性可以起作用,而且像 你絕大多數(shù)的領(lǐng)域模型, 指令也許是你想使用一成不變的類的地方。 通常包含引用或查詢數(shù) 據(jù)的表很少或基本不變的話對一成不變的類來說是合適的。 構(gòu)造方法注入允許你在初始化時 為類設(shè)置屬性的值,而不用暴露出公有方法。MyBatis 也支持私有屬性和私有 JavaBeans 屬 性來達(dá)到這個目的,但是一些人更青睞構(gòu)造方法注入。構(gòu)造方法元素支持這個。

看看下面這個構(gòu)造方法:

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

為了向這個構(gòu)造方法中注入結(jié)果,MyBatis 需要通過它的參數(shù)的類型來標(biāo)識構(gòu)造方法。 Java 沒有自查(反射)參數(shù)名的方法。所以當(dāng)創(chuàng)建一個構(gòu)造方法元素時,保證參數(shù)是按順序 排列的,而且數(shù)據(jù)類型也是確定的。

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

剩余的屬性和規(guī)則和固定的 id 和 result 元素是相同的。

屬性 描述
column 來自數(shù)據(jù)庫的類名,或重命名的列標(biāo)簽。這和通常傳遞給 resultSet.getString(columnName)方法的字符串是相同的。
javaType 一個 Java 類的完全限定名,或一個類型別名(參考上面內(nèi)建類型別名的列表)。 如果你映射到一個 JavaBean,MyBatis 通常可以斷定類型。然而,如 果你映射到的是 HashMap,那么你應(yīng)該明確地指定 javaType 來保證所需的 行為。
jdbcType 在這個表格之前的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅僅 需要對插入, 更新和刪除操作可能為空的列進(jìn)行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定這個類型-但 僅僅對可能為空的值。
typeHandler 我們在前面討論過默認(rèn)的類型處理器。使用這個屬性,你可以覆蓋默認(rèn)的 類型處理器。 這個屬性值是類的完全限定名或者是一個類型處理器的實(shí)現(xiàn), 或者是類型別名。
select The ID of another mapped statement that will load the complex type required by this property mapping. The values retrieved from columns specified in the column attribute will be passed to the target select statement as parameters. See the Association element for more.
resultMap This is the ID of a ResultMap that can map the nested results of this argument into an appropriate object graph. This is an alternative to using a call to another select statement. It allows you to join multiple tables together into a single ResultSet. Such a ResultSet will contain duplicated, repeating groups of data that needs to be decomposed and mapped properly to a nested object graph. To facilitate this, MyBatis lets you "chain" result maps together, to deal with the nested results. See the Association element below for more.

關(guān)聯(lián)

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

關(guān)聯(lián)元素處理"有一個"類型的關(guān)系。比如,在我們的示例中,一個博客有一個用戶。 關(guān)聯(lián)映射就工作于這種結(jié)果之上。你指定了目標(biāo)屬性,來獲取值的列,屬性的 java 類型(很 多情況下 MyBatis 可以自己算出來) ,如果需要的話還有 jdbc 類型,如果你想覆蓋或獲取的 結(jié)果值還需要類型控制器。

關(guān)聯(lián)中不同的是你需要告訴 MyBatis 如何加載關(guān)聯(lián)。MyBatis 在這方面會有兩種不同的 方式:

  • 嵌套查詢:通過執(zhí)行另外一個 SQL 映射語句來返回預(yù)期的復(fù)雜類型。
  • 嵌套結(jié)果:使用嵌套結(jié)果映射來處理重復(fù)的聯(lián)合結(jié)果的子集。首先,然讓我們來查看這個元素的屬性。所有的你都會看到,它和普通的只由 select 和

resultMap 屬性的結(jié)果映射不同。

屬性 描述
property 映射到列結(jié)果的字段或?qū)傩?。如果匹配的是存在?和給定名稱相同的 property JavaBeans 的屬性, 那么就會使用。 否則 MyBatis 將會尋找給定名稱的字段。 這兩種情形你可以使用通常點(diǎn)式的復(fù)雜屬性導(dǎo)航。比如,你可以這樣映射 一 些 東 西 :" username ", 或 者 映 射 到 一 些 復(fù) 雜 的 東 西 : "address.street.number" 。
javaType 一個 Java 類的完全限定名,或一個類型別名(參考上面內(nèi)建類型別名的列 表) 。如果你映射到一個 JavaBean,MyBatis 通常可以斷定類型。然而,如 javaType 果你映射到的是 HashMap,那么你應(yīng)該明確地指定 javaType 來保證所需的 行為。
jdbcType 在這個表格之前的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅僅 需要對插入, 更新和刪除操作可能為空的列進(jìn)行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定這個類型-但 僅僅對可能為空的值。
typeHandler 我們在前面討論過默認(rèn)的類型處理器。使用這個屬性,你可以覆蓋默認(rèn)的 typeHandler 類型處理器。 這個屬性值是類的完全限定名或者是一個類型處理器的實(shí)現(xiàn), 或者是類型別名。

關(guān)聯(lián)的嵌套查詢

屬性 描述
column 來自數(shù)據(jù)庫的類名,或重命名的列標(biāo)簽。這和通常傳遞給 resultSet.getString(columnName)方法的字符串是相同的。 column 注 意 : 要 處 理 復(fù) 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= " {prop1=col1,prop2=col2} " 這種語法來傳遞給嵌套查詢語 句。這會引起 prop1 和 prop2 以參數(shù)對象形式來設(shè)置給目標(biāo)嵌套查詢語句。
select 另外一個映射語句的 ID,可以加載這個屬性映射需要的復(fù)雜類型。獲取的 在列屬性中指定的列的值將被傳遞給目標(biāo) select 語句作為參數(shù)。表格后面 有一個詳細(xì)的示例。 select 注 意 : 要 處 理 復(fù) 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= " {prop1=col1,prop2=col2} " 這種語法來傳遞給嵌套查詢語 句。這會引起 prop1 和 prop2 以參數(shù)對象形式來設(shè)置給目標(biāo)嵌套查詢語句。
fetchType Optional. Valid values are lazy and eager. If present, it supersedes the global configuration parameter lazyLoadingEnabled for this mapping.

示例:

<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>

我們有兩個查詢語句:一個來加載博客,另外一個來加載作者,而且博客的結(jié)果映射描 述了"selectAuthor"語句應(yīng)該被用來加載它的 author 屬性。

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

這種方式很簡單, 但是對于大型數(shù)據(jù)集合和列表將不會表現(xiàn)很好。 問題就是我們熟知的 "N+1 查詢問題"。概括地講,N+1 查詢問題可以是這樣引起的:

  • 你執(zhí)行了一個單獨(dú)的 SQL 語句來獲取結(jié)果列表(就是"+1")。
  • 對返回的每條記錄,你執(zhí)行了一個查詢語句來為每個加載細(xì)節(jié)(就是"N")。

這個問題會導(dǎo)致成百上千的 SQL 語句被執(zhí)行。這通常不是期望的。

MyBatis 能延遲加載這樣的查詢就是一個好處,因此你可以分散這些語句同時運(yùn)行的消 耗。然而,如果你加載一個列表,之后迅速迭代來訪問嵌套的數(shù)據(jù),你會調(diào)用所有的延遲加 載,這樣的行為可能是很糟糕的。

所以還有另外一種方法。

關(guān)聯(lián)的嵌套結(jié)果

屬性 描述
resultMap 這是結(jié)果映射的 ID,可以映射關(guān)聯(lián)的嵌套結(jié)果到一個合適的對象圖中。這 是一種替代方法來調(diào)用另外一個查詢語句。這允許你聯(lián)合多個表來合成到 resultMap 一個單獨(dú)的結(jié)果集。這樣的結(jié)果集可能包含重復(fù),數(shù)據(jù)的重復(fù)組需要被分 解,合理映射到一個嵌套的對象圖。為了使它變得容易,MyBatis 讓你"鏈 接"結(jié)果映射,來處理嵌套結(jié)果。一個例子會很容易來仿照,這個表格后 面也有一個示例。
columnPrefix When joining multiple tables, you would have to use column alias to avoid duplicated column names in the ResultSet. Specifying columnPrefix allows you to map such columns to an external resultMap. Please see the example explained later in this section.
notNullColumn By default a child object is created only if at least one of the columns mapped to the child's properties is non null. With this attribute you can change this behaviour by specifiying which columns must have a value so MyBatis will create a child object only if any of those columns is not null. Multiple column names can be specified using a comma as a separator. Default value: unset.
autoMapping If present, MyBatis will enable or disable auto-mapping when mapping the result to this property. This attribute overrides the global autoMappingBehavior. Note that it has no effect on an external resultMap, so it is pointless to use it with select or resultMap attribute. Default value: unset.

在上面你已經(jīng)看到了一個非常復(fù)雜的嵌套關(guān)聯(lián)的示例。 下面這個是一個非常簡單的示例 來說明它如何工作。代替了執(zhí)行一個分離的語句,我們聯(lián)合博客表和作者表在一起,就像:

<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>

注意這個聯(lián)合查詢, 以及采取保護(hù)來確保所有結(jié)果被唯一而且清晰的名字來重命名。 這使得映射非常簡單。現(xiàn)在我們可以映射這個結(jié)果:

<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>

在上面的示例中你可以看到博客的作者關(guān)聯(lián)代表著"authorResult"結(jié)果映射來加載作 者實(shí)例。

非常重要: 在嵌套據(jù)誒過映射中 id 元素扮演了非常重要的角色。應(yīng)應(yīng)該通常指定一個 或多個屬性,它們可以用來唯一標(biāo)識結(jié)果。實(shí)際上就是如果你離開她了,但是有一個嚴(yán)重的 性能問題時 MyBatis 仍然可以工作。選擇的屬性越少越好,它們可以唯一地標(biāo)識結(jié)果。主鍵 就是一個顯而易見的選擇(盡管是聯(lián)合主鍵)。

現(xiàn)在,上面的示例用了外部的結(jié)果映射元素來映射關(guān)聯(lián)。這使得 Author 結(jié)果映射可以 重用。然而,如果你不需要重用它的話,或者你僅僅引用你所有的結(jié)果映射合到一個單獨(dú)描 述的結(jié)果映射中。你可以嵌套結(jié)果映射。這里給出使用這種方式的相同示例:

<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>

What if the blog has a co-author? The select statement would look like:

<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>

Recall that the resultMap for Author is defined as follows.

<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>

Because the column names in the results differ from the columns defined in the resultMap, you need to specify columnPrefix to reuse the resultMap for mapping co-author results.

<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>

上面你已經(jīng)看到了如何處理"有一個"類型關(guān)聯(lián)。但是"有很多個"是怎樣的?下面這 個部分就是來討論這個主題的。

集合

上一篇:XML 映射配置文件下一篇:Java API