MyBatis XML映射文件

2022-05-07 17:32 更新

Mapper XML 文件

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

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

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

下一部分將從語(yǔ)句本身開(kāi)始來(lái)描述每個(gè)元素的細(xì)節(jié)。

select

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

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

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

注意參數(shù)符號(hào):

#{id}

這就告訴 MyBatis 創(chuàng)建一個(gè)預(yù)處理語(yǔ)句參數(shù),通過(guò) JDBC,這樣的一個(gè)參數(shù)在 SQL 中會(huì)由一個(gè) "?" 來(lái)標(biāo)識(shí),并被傳遞到一個(gè)新的預(yù)處理語(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 的代碼來(lái)提取結(jié)果并將它們映射到對(duì)象實(shí)例中,這就是 MyBatis 節(jié)省你時(shí)間的地方。我們需要深入了解參數(shù)和結(jié)果映射,細(xì)節(jié)部分我們下面來(lái)了解。

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

insert, update 和 delete

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

下面就是 insert,update 和 delete 語(yǔ)句的示例:

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

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

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

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

如果你的數(shù)據(jù)庫(kù)還支持多行插入, 你也可以傳入一個(gè)Authors數(shù)組或集合,并返回自動(dòng)生成的主鍵。

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

在上面的示例中,selectKey 元素將會(huì)首先運(yùn)行,Author 的 id 會(huì)被設(shè)置,然后插入語(yǔ)句會(huì)被調(diào)用。這給你了一個(gè)和數(shù)據(jù)庫(kù)中來(lái)處理自動(dòng)生成的主鍵類似的行為,避免了使 Java 代碼變得復(fù)雜。

selectKey 元素描述如下:

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

selectKey 的屬性

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

sql

這個(gè)元素可以被用來(lái)定義可重用的 SQL 代碼段,可以包含在其他語(yǔ)句中。它可以靜態(tài)地(在加載階段)參數(shù)化。不同的屬性值可以在include實(shí)例中有所不同。 比如:

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

這個(gè) SQL 片段可以被包含在其他語(yǔ)句中,例如:

<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 子句中的屬性值,例如:

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

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

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

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

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

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

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

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

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

像 MyBatis 的剩余部分一樣,javaType 通常可以從參數(shù)對(duì)象中來(lái)去確定,前提是只要對(duì)象不是一個(gè) HashMap。那么 javaType 應(yīng)該被確定來(lái)保證使用正確類型處理器。

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

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

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

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

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

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

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

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

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

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

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

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

字符串替換

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

ORDER BY ${columnName}

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

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

Result Maps

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

你已經(jīng)看到簡(jiǎn)單映射語(yǔ)句的示例了,但沒(méi)有明確的 resultMap。比如:

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

這樣一個(gè)語(yǔ)句簡(jiǎn)單作用于所有列被自動(dòng)映射到 HashMap 的鍵上,這由 ?resultType? 屬性 指定。這在很多情況下是有用的,但是 HashMap 不能很好描述一個(gè)領(lǐng)域模型。那樣你的應(yīng) 用程序?qū)?huì)使用 JavaBeans 或 POJOs(Plain Old Java Objects,普通 Java 對(duì)象)來(lái)作為領(lǐng)域 模型。MyBatis 對(duì)兩者都支持??纯聪旅孢@個(gè) 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ī)范,上面這個(gè)類有 3 個(gè)屬性:?id?,?username ?和? hashedPassword?。這些 在 select 語(yǔ)句中會(huì)精確匹配到列名。

這樣的一個(gè) JavaBean 可以被映射到結(jié)果集,就像映射到 HashMap 一樣簡(jiǎn)單。

<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 會(huì)在幕后自動(dòng)創(chuàng)建一個(gè) ResultMap,基于屬性名來(lái)映射列到 JavaBean 的屬性上。如果列名沒(méi)有精確匹配,你可以在列名上使用 select 字句的別名(一個(gè) 基本的 SQL 特性)來(lái)匹配標(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)了解了很多了,但是你還沒(méi)有真正的看到一個(gè)。這些簡(jiǎn) 單的示例不需要比你看到的更多東西。 只是出于示例的原因, 讓我們來(lái)看看最后一個(gè)示例中 外部的 resultMap 是什么樣子的,這也是解決列名不匹配的另外一種方式。

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

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

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

如果世界總是這么簡(jiǎn)單就好了。

高級(jí)結(jié)果映射

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

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

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

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

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

resultMap

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

ResultMap Attributes

屬性 描述
id 此名稱空間中的唯一標(biāo)識(shí)符,可用于引用此結(jié)果映射。
type 一個(gè)完全特定的 Java 類名,或者一個(gè)類型別名(參見(jiàn)上表中的內(nèi)置類型別名列表)。
autoMapping 如果存在,MyBatis 將啟用或禁用這個(gè) ResultMap 的自動(dòng)操作。此屬性覆蓋全局?autoMappingBehavior?。默認(rèn)值:未設(shè)置的。

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

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

id & result

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

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

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

每個(gè)都有一些屬性:

?Id and Result Attributes?

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

支持的 JDBC 類型

為了未來(lái)的參考,MyBatis 通過(guò)包含的 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>

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

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

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

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

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

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

屬性 描述
column 來(lái)自數(shù)據(jù)庫(kù)的類名,或重命名的列標(biāo)簽。這和通常傳遞給 ?resultSet.getString(columnName)?方法的字符串是相同的。
javaType 一個(gè) Java 類的完全限定名,或一個(gè)類型別名(參考上面內(nèi)建類型別名的列表)。 如果你映射到一個(gè) JavaBean,MyBatis 通??梢詳喽愋汀H欢?如 果你映射到的是 HashMap,那么你應(yīng)該明確地指定 javaType 來(lái)保證所需的 行為。
jdbcType 在這個(gè)表格之前的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅僅 需要對(duì)插入, 更新和刪除操作可能為空的列進(jìn)行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定這個(gè)類型-但 僅僅對(duì)可能為空的值。
typeHandler 我們?cè)谇懊嬗懻撨^(guò)默認(rèn)的類型處理器。使用這個(gè)屬性,你可以覆蓋默認(rèn)的 類型處理器。 這個(gè)屬性值是類的完全限定名或者是一個(gè)類型處理器的實(shí)現(xiàn), 或者是類型別名。
select 另一個(gè)映射語(yǔ)句的 ID,該語(yǔ)句將加載此屬性映射所需的復(fù)雜類型。從列屬性中指定的列檢索的值將作為參數(shù)傳遞給目標(biāo) select 語(yǔ)句。更多信息請(qǐng)參見(jiàn)關(guān)聯(lián)元素。
resultMap 這是 ResultMap 的 ID,它可以將此參數(shù)的嵌套結(jié)果映射到適當(dāng)?shù)膶?duì)象圖中。這是對(duì)另一個(gè) select 語(yǔ)句調(diào)用的替代方法。它允許您將多個(gè)表連接到一個(gè)結(jié)果集中。這樣的ResultSet 將包含重復(fù)的、重復(fù)的數(shù)據(jù)組,需要對(duì)這些數(shù)據(jù)進(jìn)行分解并正確地映射到嵌套的對(duì)象圖中。為了實(shí)現(xiàn)這一點(diǎn),MyBatis 允許將結(jié)果映射“鏈”在一起,以處理嵌套的結(jié)果。更多信息請(qǐng)參見(jiàn)下面的關(guān)聯(lián)元素。

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

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

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

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

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

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

屬性 描述
column 來(lái)自數(shù)據(jù)庫(kù)的類名,或重命名的列標(biāo)簽。這和通常傳遞給 ?resultSet.getString(columnName)?方法的字符串是相同的。 column 注 意 : 要 處 理 復(fù) 合 主 鍵 , 你 可 以 指 定 多 個(gè) 列 名 通 過(guò)? column= " {prop1=col1,prop2=col2} "? 這種語(yǔ)法來(lái)傳遞給嵌套查詢語(yǔ) 句。這會(huì)引起 prop1 和 prop2 以參數(shù)對(duì)象形式來(lái)設(shè)置給目標(biāo)嵌套查詢語(yǔ)句。
select 另外一個(gè)映射語(yǔ)句的 ID,可以加載這個(gè)屬性映射需要的復(fù)雜類型。獲取的 在列屬性中指定的列的值將被傳遞給目標(biāo) select 語(yǔ)句作為參數(shù)。表格后面 有一個(gè)詳細(xì)的示例。 select 注 意 : 要 處 理 復(fù) 合 主 鍵 , 你 可 以 指 定 多 個(gè) 列 名 通 過(guò) ?column= " {prop1=col1,prop2=col2} " ?這種語(yǔ)法來(lái)傳遞給嵌套查詢語(yǔ) 句。這會(huì)引起 prop1 和 prop2 以參數(shù)對(duì)象形式來(lái)設(shè)置給目標(biāo)嵌套查詢語(yǔ)句。
fetchType 可選的。有效值是 lazy 的和 eager 的。如果存在,它將替代此映射的全局配置參數(shù)? 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>

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

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

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

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

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

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

所以還有另外一種方法。

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

屬性 描述
resultMap 這是結(jié)果映射的 ID,可以映射關(guān)聯(lián)的嵌套結(jié)果到一個(gè)合適的對(duì)象圖中。這 是一種替代方法來(lái)調(diào)用另外一個(gè)查詢語(yǔ)句。這允許你聯(lián)合多個(gè)表來(lái)合成到 ?resultMap? 一個(gè)單獨(dú)的結(jié)果集。這樣的結(jié)果集可能包含重復(fù),數(shù)據(jù)的重復(fù)組需要被分 解,合理映射到一個(gè)嵌套的對(duì)象圖。為了使它變得容易,MyBatis 讓你"鏈 接"結(jié)果映射,來(lái)處理嵌套結(jié)果。一個(gè)例子會(huì)很容易來(lái)仿照,這個(gè)表格后 面也有一個(gè)示例。
columnPrefix 在連接多個(gè)表時(shí),必須使用列別名以避免結(jié)果集中的重復(fù)列名。指定 ?columnPrefix? 允許您將這些列映射到外部?resultMap?。請(qǐng)參閱本節(jié)后面解釋的示例。
notNullColumn 默認(rèn)情況下,只有在映射到子對(duì)象屬性的至少一個(gè)列非空時(shí),才會(huì)創(chuàng)建子對(duì)象。有了這個(gè)屬性,您可以通過(guò)指定哪些列必須有一個(gè)值來(lái)改變這種行為,這樣 MyBatis 將只在這些列中的任何一列不為空時(shí)創(chuàng)建子對(duì)象??梢允褂枚禾?hào)作為分隔符指定多個(gè)列名。默認(rèn)值:未設(shè)置的。
autoMapping 如果存在,MyBatis 將在將結(jié)果映射到此屬性時(shí)啟用或禁用自動(dòng)映射。此屬性覆蓋全局 ?autoMappingBehavior?。注意,它對(duì)外部? resultMap? 沒(méi)有影響,因此與?select?或 ?resultMap? 屬性一起使用是沒(méi)有意義的。默認(rèn)值:未設(shè)置的。

在上面你已經(jīng)看到了一個(gè)非常復(fù)雜的嵌套關(guān)聯(lián)的示例。 下面這個(gè)是一個(gè)非常簡(jiǎn)單的示例 來(lái)說(shuō)明它如何工作。代替了執(zhí)行一個(gè)分離的語(yǔ)句,我們聯(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>

注意這個(gè)聯(lián)合查詢, 以及采取保護(hù)來(lái)確保所有結(jié)果被唯一而且清晰的名字來(lái)重命名。 這使得映射非常簡(jiǎn)單?,F(xiàn)在我們可以映射這個(gè)結(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é)果映射來(lái)加載作 者實(shí)例。

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

現(xiàn)在,上面的示例用了外部的結(jié)果映射元素來(lái)映射關(guān)聯(lián)。這使得 Author 結(jié)果映射可以 重用。然而,如果你不需要重用它的話,或者你僅僅引用你所有的結(jié)果映射合到一個(gè)單獨(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>

如果博客有一個(gè)共同作者呢?select語(yǔ)句如下:

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

由于結(jié)果中的列名與resultMap中定義的列不同,因此您需要指定columnPrefix來(lái)重新使用resultMap,以便映射合Author的結(jié)果。

<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)看到了如何處理"有一個(gè)"類型關(guān)聯(lián)。但是"有很多個(gè)"是怎樣的?下面這 個(gè)部分就是來(lái)討論這個(gè)主題的。

集合

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

集合元素的作用幾乎和關(guān)聯(lián)是相同的。實(shí)際上,它們也很相似,文檔的異同是多余的。 所以我們更多關(guān)注于它們的不同。

我們來(lái)繼續(xù)上面的示例,一個(gè)博客只有一個(gè)作者。但是博客有很多文章。在博客類中, 這可以由下面這樣的寫(xiě)法來(lái)表示:

    private List posts;

要映射嵌套結(jié)果集合到 List 中,我們使用集合元素。就像關(guān)聯(lián)元素一樣,我們可以從 連接中使用嵌套查詢,或者嵌套結(jié)果。

集合的嵌套查詢

首先,讓我們看看使用嵌套查詢來(lái)為博客加載文章。

<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="Blog">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

這里你應(yīng)該注意很多東西,但大部分代碼和上面的關(guān)聯(lián)元素是非常相似的。首先,你應(yīng) 該注意我們使用的是集合元素。然后要注意那個(gè)新的"ofType"屬性。這個(gè)屬性用來(lái)區(qū)分 JavaBean(或字段)屬性類型和集合包含的類型來(lái)說(shuō)是很重要的。所以你可以讀出下面這個(gè) 映射:

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

讀作: "在 Post 類型的 ArrayList 中的 posts 的集合。"

javaType 屬性是不需要的,因?yàn)?MyBatis 在很多情況下會(huì)為你算出來(lái)。所以你可以縮短 寫(xiě)法:

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

集合的嵌套結(jié)果

至此,你可以猜測(cè)集合的嵌套結(jié)果是如何來(lái)工作的,因?yàn)樗完P(guān)聯(lián)完全相同,除了它應(yīng) 用了一個(gè)"ofType"屬性

First, let's look at the 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>

我們又一次聯(lián)合了博客表和文章表,而且關(guān)注于保證特性,結(jié)果列標(biāo)簽的簡(jiǎn)單映射?,F(xiàn) 在用文章映射集合映射博客,可以簡(jiǎn)單寫(xiě)為:

<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 元素的重要性,如果你不記得了,請(qǐng)閱讀上面的關(guān)聯(lián)部分。

同樣, 如果你引用更長(zhǎng)的形式允許你的結(jié)果映射的更多重用, 你可以使用下面這個(gè)替代 的映射:

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

注意 這個(gè)對(duì)你所映射的內(nèi)容沒(méi)有深度,廣度或關(guān)聯(lián)和集合相聯(lián)合的限制。當(dāng)映射它們 時(shí)你應(yīng)該在大腦中保留它們的表現(xiàn)。 你的應(yīng)用在找到最佳方法前要一直進(jìn)行的單元測(cè)試和性 能測(cè)試。好在 myBatis 讓你后來(lái)可以改變想法,而不對(duì)你的代碼造成很小(或任何)影響。

高級(jí)關(guān)聯(lián)和集合映射是一個(gè)深度的主題。文檔只能給你介紹到這了。加上一點(diǎn)聯(lián)系,你 會(huì)很快清楚它們的用法。

鑒別器

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

有時(shí)一個(gè)單獨(dú)的數(shù)據(jù)庫(kù)查詢也許返回很多不同 (但是希望有些關(guān)聯(lián)) 數(shù)據(jù)類型的結(jié)果集。 鑒別器元素就是被設(shè)計(jì)來(lái)處理這個(gè)情況的, 還有包括類的繼承層次結(jié)構(gòu)。 鑒別器非常容易理 解,因?yàn)樗谋憩F(xiàn)很像 Java 語(yǔ)言中的 switch 語(yǔ)句。

定義鑒別器指定了 column 和 javaType 屬性。 列是 MyBatis 查找比較值的地方。 JavaType 是需要被用來(lái)保證等價(jià)測(cè)試的合適類型(盡管字符串在很多情形下都會(huì)有用)。比如:

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

在這個(gè)示例中, MyBatis 會(huì)從結(jié)果集中得到每條記錄, 然后比較它的 vehicle 類型的值。 如果它匹配任何一個(gè)鑒別器的實(shí)例,那么就使用這個(gè)實(shí)例指定的結(jié)果映射。換句話說(shuō),這樣 做完全是剩余的結(jié)果映射被忽略(除非它被擴(kuò)展,這在第二個(gè)示例中討論) 。如果沒(méi)有任何 一個(gè)實(shí)例相匹配,那么 MyBatis 僅僅使用鑒別器塊外定義的結(jié)果映射。所以,如果 carResult 按如下聲明:

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

那么只有 doorCount 屬性會(huì)被加載。這步完成后完整地允許鑒別器實(shí)例的獨(dú)立組,盡管 和父結(jié)果映射可能沒(méi)有什么關(guān)系。這種情況下,我們當(dāng)然知道 cars 和 vehicles 之間有關(guān)系, 如 Car 是一個(gè) Vehicle 實(shí)例。因此,我們想要剩余的屬性也被加載。我們?cè)O(shè)置的結(jié)果映射的 簡(jiǎn)單改變?nèi)缦隆?/p>

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

現(xiàn)在 vehicleResult 和 carResult 的屬性都會(huì)被加載了。

盡管曾經(jīng)有些人會(huì)發(fā)現(xiàn)這個(gè)外部映射定義會(huì)多少有一些令人厭煩之處。 因此還有另外一 種語(yǔ)法來(lái)做簡(jiǎn)潔的映射風(fēng)格。比如:

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

要記得 這些都是結(jié)果映射, 如果你不指定任何結(jié)果, 那么 MyBatis 將會(huì)為你自動(dòng)匹配列 和屬性。所以這些例子中的大部分是很冗長(zhǎng)的,而其實(shí)是不需要的。也就是說(shuō),很多數(shù)據(jù)庫(kù) 是很復(fù)雜的,我們不太可能對(duì)所有示例都能依靠它。

自動(dòng)映射

正如你在前面一節(jié)看到的,在簡(jiǎn)單的場(chǎng)景下,MyBatis 可以替你自動(dòng)映射查詢結(jié)果。 如果遇到復(fù)雜的場(chǎng)景,你需要構(gòu)建一個(gè) result map。 但是在本節(jié)你將看到,你也可以混合使用這兩種策略。 讓我們到深一點(diǎn)的層面上看看自動(dòng)映射是怎樣工作的。

當(dāng)自動(dòng)映射查詢結(jié)果時(shí),MyBatis 會(huì)獲取 sql 返回的列名并在 java 類中查找相同名字的屬性(忽略大小寫(xiě))。 這意味著如果 Mybatis 發(fā)現(xiàn)了 _ID_ 列和 _id_ 屬性,Mybatis會(huì)將_ID_ 的值賦給 id。

通常數(shù)據(jù)庫(kù)列使用大寫(xiě)單詞命名,單詞間用下劃線分隔;而java屬性一般遵循駝峰命名法。 為了在這兩種命名方式之間啟用自動(dòng)映射,需要將 mapUnderscoreToCamelCase設(shè)置為 true。

自動(dòng)映射甚至在特定的 result map下也能工作。在這種情況下,對(duì)于每一個(gè) result map,所有的 ResultSet 提供的列, 如果沒(méi)有被手工映射,則將被自動(dòng)映射。自動(dòng)映射處理完畢后手工映射才會(huì)被處理。 在接下來(lái)的例子中, id 和 _userName_列將被自動(dòng)映射, _hashedpassword 列將根據(jù)配置映射。

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

有三個(gè)自動(dòng)映射級(jí)別:

  • NONE - 禁用自動(dòng)映射,只有手動(dòng)映射屬性才會(huì)被設(shè)置。
  • PARTIAL - 將自動(dòng)映射結(jié)果,除了那些嵌套結(jié)果映射(連接)內(nèi)的結(jié)果。
  • FULL - 自動(dòng)映射一切

默認(rèn)值是 PARTIAL,這是有原因的。 使用 FULL 時(shí),將在處理連接結(jié)果時(shí)執(zhí)行自動(dòng)映射,并且連接會(huì)檢索同一行中的多個(gè)不同實(shí)體的數(shù)據(jù),因此可能會(huì)導(dǎo)致不需要的映射。 要了解風(fēng)險(xiǎn),請(qǐng)查看下面的示例:

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

在結(jié)果中 Blog 和 Author 均將自動(dòng)映射。但是注意 Author 有一個(gè) id 屬性,在 ResultSet 中有一個(gè)列名為 id, 所以 Author 的 id 將被填充為 Blog 的 id,這不是你所期待的。所以需要謹(jǐn)慎使用 FULL。

通過(guò)添加 autoMapping 屬性可以忽略自動(dòng)映射等級(jí)配置,你可以啟用或者禁用自動(dòng)映射指定的 ResultMap。

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

緩存

MyBatis 包含一個(gè)非常強(qiáng)大的查詢緩存特性,它可以非常方便地配置和定制。MyBatis 3 中的緩存實(shí)現(xiàn)的很多改進(jìn)都已經(jīng)實(shí)現(xiàn)了,使得它更加強(qiáng)大而且易于配置。

默認(rèn)情況下是沒(méi)有開(kāi)啟緩存的,除了局部的 session 緩存,可以增強(qiáng)變現(xiàn)而且處理循環(huán) 依賴也是必須的。要開(kāi)啟二級(jí)緩存,你需要在你的 SQL 映射文件中添加一行:

<cache/>

字面上看就是這樣。這個(gè)簡(jiǎn)單語(yǔ)句的效果如下:

  • 映射語(yǔ)句文件中的所有 select 語(yǔ)句將會(huì)被緩存。
  • 映射語(yǔ)句文件中的所有 insert,update 和 delete 語(yǔ)句會(huì)刷新緩存。
  • 緩存會(huì)使用 Least Recently Used(LRU,最近最少使用的)算法來(lái)收回。
  • 根據(jù)時(shí)間表(比如 no Flush Interval,沒(méi)有刷新間隔), 緩存不會(huì)以任何時(shí)間順序 來(lái)刷新。
  • 緩存會(huì)存儲(chǔ)列表集合或?qū)ο?無(wú)論查詢方法返回什么)的 1024 個(gè)引用。
  • 緩存會(huì)被視為是 read/write(可讀/可寫(xiě))的緩存,意味著對(duì)象檢索不是共享的,而 且可以安全地被調(diào)用者修改,而不干擾其他調(diào)用者或線程所做的潛在修改。

所有的這些屬性都可以通過(guò)緩存元素的屬性來(lái)修改。比如:

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

這個(gè)更高級(jí)的配置創(chuàng)建了一個(gè) FIFO 緩存,并每隔 60 秒刷新,存數(shù)結(jié)果對(duì)象或列表的 512 個(gè)引用,而且返回的對(duì)象被認(rèn)為是只讀的,因此在不同線程中的調(diào)用者之間修改它們會(huì) 導(dǎo)致沖突。

可用的收回策略有:

  • LRU – 最近最少使用的:移除最長(zhǎng)時(shí)間不被使用的對(duì)象。
  • FIFO – 先進(jìn)先出:按對(duì)象進(jìn)入緩存的順序來(lái)移除它們。
  • SOFT – 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對(duì)象。
  • WEAK – 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對(duì)象。

默認(rèn)的是 LRU。

flushInterval (刷新間隔)可以被設(shè)置為任意的正整數(shù),而且它們代表一個(gè)合理的毫秒 形式的時(shí)間段。默認(rèn)情況是不設(shè)置,也就是沒(méi)有刷新間隔,緩存僅僅調(diào)用語(yǔ)句時(shí)刷新。

size(引用數(shù)目)可以被設(shè)置為任意正整數(shù),要記住你緩存的對(duì)象數(shù)目和你運(yùn)行環(huán)境的 可用內(nèi)存資源數(shù)目。默認(rèn)值是 1024。

readOnly (只讀)屬性可以被設(shè)置為 true 或 false。只讀的緩存會(huì)給所有調(diào)用者返回緩 存對(duì)象的相同實(shí)例。因此這些對(duì)象不能被修改。這提供了很重要的性能優(yōu)勢(shì)??勺x寫(xiě)的緩存 會(huì)返回緩存對(duì)象的拷貝(通過(guò)序列化) 。這會(huì)慢一些,但是安全,因此默認(rèn)是 false。

使用自定義緩存

除了這些自定義緩存的方式, 你也可以通過(guò)實(shí)現(xiàn)你自己的緩存或?yàn)槠渌谌骄彺娣桨?創(chuàng)建適配器來(lái)完全覆蓋緩存行為。

<cache type="com.domain.something.MyCustomCache"/>

這個(gè)示 例展 示了 如何 使用 一個(gè) 自定義 的緩 存實(shí) 現(xiàn)。type 屬 性指 定的 類必 須實(shí)現(xiàn) org.mybatis.cache.Cache 接口。這個(gè)接口是 MyBatis 框架中很多復(fù)雜的接口之一,但是簡(jiǎn)單 給定它做什么就行。

    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();
    }

要配置你的緩存, 簡(jiǎn)單和公有的 JavaBeans 屬性來(lái)配置你的緩存實(shí)現(xiàn), 而且是通過(guò) cache 元素來(lái)傳遞屬性, 比如, 下面代碼會(huì)在你的緩存實(shí)現(xiàn)中調(diào)用一個(gè)稱為 "setCacheFile(String file)" 的方法:

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

你可以使用所有簡(jiǎn)單類型作為 JavaBeans 的屬性,MyBatis 會(huì)進(jìn)行轉(zhuǎn)換。

記得緩存配置和緩存實(shí)例是綁定在 SQL 映射文件的命名空間是很重要的。因此,所有 在相同命名空間的語(yǔ)句正如綁定的緩存一樣。 語(yǔ)句可以修改和緩存交互的方式, 或在語(yǔ)句的 語(yǔ)句的基礎(chǔ)上使用兩種簡(jiǎn)單的屬性來(lái)完全排除它們。默認(rèn)情況下,語(yǔ)句可以這樣來(lái)配置:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

因?yàn)槟切┦悄J(rèn)的,你明顯不能明確地以這種方式來(lái)配置一條語(yǔ)句。相反,如果你想改 變默認(rèn)的行為,只能設(shè)置 flushCache 和 useCache 屬性。比如,在一些情況下你也許想排除 從緩存中查詢特定語(yǔ)句結(jié)果,或者你也許想要一個(gè)查詢語(yǔ)句來(lái)刷新緩存。相似地,你也許有 一些更新語(yǔ)句依靠執(zhí)行而不需要刷新緩存。

參照緩存

回想一下上一節(jié)內(nèi)容, 這個(gè)特殊命名空間的唯一緩存會(huì)被使用或者刷新相同命名空間內(nèi) 的語(yǔ)句。也許將來(lái)的某個(gè)時(shí)候,你會(huì)想在命名空間中共享相同的緩存配置和實(shí)例。在這樣的 情況下你可以使用 cache-ref 元素來(lái)引用另外一個(gè)緩存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)