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é)。
查詢語(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)分隔的。 |
數(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 類型。 |
這個(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>
前面的所有語(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)。
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)單就好了。
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 元素的概念視圖
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é)果將包成這種類型
collection
– 復(fù)雜類型的集
discriminator
– 使用結(jié)果值來(lái)決定使用哪個(gè)結(jié)果映射
case
– 基于某些值的結(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 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),或者是類型別名。 |
為了未來(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
|
<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ì)有兩種不同的 方式:
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), 或者是類型別名。 |
屬性 | 描述 |
---|---|
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)題可以是這樣引起的:
這個(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)用所有的延遲加 載,這樣的行為可能是很糟糕的。
所以還有另外一種方法。
屬性 | 描述 |
---|---|
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"/>
至此,你可以猜測(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ì)所有示例都能依靠它。
正如你在前面一節(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ǔ)句的效果如下:
所有的這些屬性都可以通過(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"/>
更多建議: