1. 簡介
1.1 什么是 MyBatis ?
- MyBatis 是支持定制化 SQL懂版、存儲過程以及高級映射的優(yōu)秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集躏率。MyBatis 可以對配置和原生Map使用簡單的 XML 或注解躯畴,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數(shù)據(jù)庫中的記錄。
2.入門
2.1 安裝
要使用 MyBatis薇芝, 只需將 mybatis-x.x.x.jar 文件置于 classpath 中即可蓬抄。
如果使用 Maven 來構(gòu)建項目,則需將下面的 dependency 代碼置于 pom.xml 文件中:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
2.2 從 XML 中構(gòu)建 SqlSessionFactory
每個基于 MyBatis 的應(yīng)用都是以一個 SqlSessionFactory 的實例為中心的夯到。SqlSessionFactory 的實例可以通過 SqlSessionFactoryBuilder 獲得嚷缭。而 SqlSessionFactoryBuilder 則可以從 XML 配置文件或一個預(yù)先定制的 Configuration 的實例構(gòu)建出 SqlSessionFactory 的實例。
從 XML 文件中構(gòu)建 SqlSessionFactory 的實例非常簡單耍贾,建議使用類路徑下的資源文件進行配置峭状。但是也可以使用任意的輸入流(InputStream)實例,包括字符串形式的文件路徑或者 file:// 的 URL 形式的文件路徑來配置逼争。MyBatis 包含一個名叫 Resources 的工具類优床,它包含一些實用方法,可使從 classpath 或其他位置加載資源文件更加容易誓焦。
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
- XML 配置文件(configuration XML)中包含了對 MyBatis 系統(tǒng)的核心設(shè)置胆敞,包含獲取數(shù)據(jù)庫連接實例的數(shù)據(jù)源(DataSource)和決定事務(wù)范圍和控制方式的事務(wù)管理器(TransactionManager)。XML 配置文件的詳細內(nèi)容后面再探討杂伟,這里先給出一個簡單的示例:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
- 當(dāng)然移层,還有很多可以在XML 文件中進行配置,上面的示例指出的則是最關(guān)鍵的部分赫粥。要注意 XML 頭部的聲明观话,用來驗證 XML 文檔正確性。environment 元素體中包含了事務(wù)管理和連接池的配置越平。mappers 元素則是包含一組 mapper 映射器(這些 mapper 的 XML 文件包含了 SQL 代碼和映射定義信息)频蛔。
2.2 不使用 XML 構(gòu)建 SqlSessionFactory
- 如果你更愿意直接從 Java 程序而不是 XML 文件中創(chuàng)建 configuration灵迫,或者創(chuàng)建你自己的 configuration 構(gòu)建器,MyBatis 也提供了完整的配置類晦溪,提供所有和 XML 文件相同功能的配置項瀑粥。
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
- 注意該例中,configuration 添加了一個映射器類(mapper class)三圆。映射器類是 Java 類狞换,它們包含 SQL 映射語句的注解從而避免了 XML 文件的依賴。不過舟肉,由于 Java 注解的一些限制加之某些 MyBatis 映射的復(fù)雜性修噪,XML 映射對于大多數(shù)高級映射(比如:嵌套 Join 映射)來說仍然是必須的。有鑒于此路媚,如果存在一個對等的 XML 配置文件的話黄琼,MyBatis 會自動查找并加載它(這種情況下, BlogMapper.xml 將會基于類路徑和 BlogMapper.class 的類名被加載進來)磷籍。具體細節(jié)稍后討論适荣。
2.3 從 SqlSessionFactory 中獲取 SqlSession
- 既然有了 SqlSessionFactory 现柠,顧名思義院领,我們就可以從中獲得 SqlSession 的實例了。SqlSession 完全包含了面向數(shù)據(jù)庫執(zhí)行 SQL 命令所需的所有方法够吩。你可以通過 SqlSession 實例來直接執(zhí)行已映射的 SQL 語句比然。例如:
SqlSession session = sqlSessionFactory.openSession();
try {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
session.close();
}
- 誠然這種方式能夠正常工作,并且對于使用舊版本 MyBatis 的用戶來說也比較熟悉周循,不過現(xiàn)在有了一種更直白的方式强法。使用對于給定語句能夠合理描述參數(shù)和返回值的接口(比如說BlogMapper.class),你現(xiàn)在不但可以執(zhí)行更清晰和類型安全的代碼湾笛,而且還不用擔(dān)心易錯的字符串字面值以及強制類型轉(zhuǎn)換饮怯。例如:
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
} finally {
session.close();
}
現(xiàn)在我們來探究一下這里到底是怎么執(zhí)行的。
2.4 探究已映射的 SQL 語句
現(xiàn)在嚎研,或許你很想知道 SqlSession 和 Mapper 到底執(zhí)行了什么操作蓖墅,而 SQL 語句映射是個相當(dāng)大的話題,可能會占去文檔的大部分篇幅临扮。不過為了讓你能夠了解個大概论矾,這里會給出幾個例子。
在上面提到的兩個例子中杆勇,一個語句應(yīng)該是通過 XML 定義贪壳,而另外一個則是通過注解定義。先看 XML 定義這個蚜退,事實上 MyBatis 提供的全部特性可以利用基于 XML 的映射語言來實現(xiàn)闰靴,這使得 MyBatis 在過去的數(shù)年間得以流行彪笼。如果你以前用過 MyBatis,這個概念應(yīng)該會比較熟悉传黄。不過 XML 映射文件已經(jīng)有了很多的改進杰扫,隨著文檔的進行會愈發(fā)清晰。這里給出一個基于 XML 映射語句的示例膘掰,它應(yīng)該可以滿足上述示例中 SqlSession 的調(diào)用章姓。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
- 對于這個簡單的例子來說似乎有點小題大做了,但實際上它是非常輕量級的识埋。在一個 XML 映射文件中凡伊,你想定義多少個映射語句都是可以的,這樣下來窒舟,XML 頭部和文檔類型聲明占去的部分就顯得微不足道了系忙。文件的剩余部分具有很好的自解釋性。在命名空間“org.mybatis.example.BlogMapper”中定義了一個名為“selectBlog”的映射語句惠豺,這樣它就允許你使用指定的完全限定名“org.mybatis.example.BlogMapper.selectBlog”來調(diào)用映射語句银还,就像上面的例子中做的那樣:
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
- 你可能注意到這和使用完全限定名調(diào)用 Java 對象的方法是相似的伏嗜,之所以這樣做是有原因的串结。這個命名可以直接映射到在命名空間中同名的 Mapper 類梨睁,并在已映射的 select 語句中的名字蜡饵、參數(shù)和返回類型匹配成方法均践。這樣你就可以向上面那樣很容易地調(diào)用這個對應(yīng) Mapper 接口的方法展姐。不過讓我們再看一遍下面的例子:
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
- 第二種方法有很多優(yōu)勢躬存,首先它不是基于字符串常量的谋作,就會更安全孝扛;其次列吼,如果你的 IDE 有代碼補全功能,那么你可以在有了已映射 SQL 語句的基礎(chǔ)之上利用這個功能苦始。
提示 命名空間的一點注釋
命名空間(Namespaces)在之前版本的 MyBatis 中是可選的寞钥,容易引起混淆因此是沒有益處的。現(xiàn)在的命名空間則是必須的陌选,目的是希望能比只是簡單的使用更長的完全限定名來區(qū)分語句更進一步理郑。
命名空間使得你所見到的接口綁定成為可能,盡管你覺得這些東西未必用得上柠贤,你還是應(yīng)該遵循這里的規(guī)定以防哪天你改變了主意香浩。出于長遠考慮,使用命名空間臼勉,并將它置于合適的 Java 包命名空間之下邻吭,你將擁有一份更加整潔的代碼并提高了 MyBatis 的可用性。
命名解析:為了減少輸入量宴霸,MyBatis 對所有的命名配置元素(包括語句囱晴,結(jié)果映射膏蚓,緩存等)使用了如下的命名解析規(guī)則。
- 完全限定名(比如“com.mypackage.MyMapper.selectAllThings”)將被直接查找并且找到即用畸写。
- 短名稱(比如“selectAllThings”)如果全局唯一也可以作為一個單獨的引用驮瞧。如果不唯一,有兩個或兩個以上的相同名稱(比如“com.foo.selectAllThings ”和“com.bar.selectAllThings”)枯芬,那么使用時就會收到錯誤報告說短名稱是不唯一的论笔,這種情況下就必須使用完全限定名。
--
- 對于像 BlogMapper 這樣的映射器類(Mapper class)來說千所,還有另一招來處理狂魔。它們的映射的語句可以不需要用 XML 來做,取而代之的是可以使用 Java 注解淫痰。比如最楷,上面的 XML 示例可被替換如下:
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
對于簡單語句來說,注解使代碼顯得更加簡潔待错,然而 Java 注解對于稍微復(fù)雜的語句就會力不從心并且會顯得更加混亂籽孙。因此,如果你需要做很復(fù)雜的事情火俄,那么最好使用 XML 來映射語句犯建。
選擇何種方式以及映射語句的定義的一致性對你來說有多重要這些完全取決于你和你的團隊。換句話說烛占,永遠不要拘泥于一種方式胎挎,你可以很輕松的在基于注解和 XML 的語句映射方式間自由移植和切換沟启。
2.4 范圍(Scope)和生命周期
- 理解我們目前已經(jīng)討論過的不同范圍和生命周期類是至關(guān)重要的忆家,因為錯誤的使用會導(dǎo)致非常嚴(yán)重的并發(fā)問題。
提示 對象生命周期和依賴注入框架
依賴注入框架可以創(chuàng)建線程安全的德迹、基于事務(wù)的 SqlSession 和映射器(mapper)并將它們直接注入到你的 bean 中芽卿,因此可以直接忽略它們的生命周期。如果對如何通過依賴注入框架來使用 MyBatis 感興趣可以研究一下 MyBatis-Spring 或 MyBatis-Guice 兩個子項目胳搞。
2.4.1 SqlSessionFactoryBuilder
- 這個類可以被實例化卸例、使用和丟棄,一旦創(chuàng)建了 SqlSessionFactory肌毅,就不再需要它了筷转。因此 SqlSessionFactoryBuilder 實例的最佳范圍是方法范圍(也就是局部方法變量)。你可以重用 SqlSessionFactoryBuilder 來創(chuàng)建多個 SqlSessionFactory 實例悬而,但是最好還是不要讓其一直存在以保證所有的 XML 解析資源開放給更重要的事情呜舒。
2.4.2 SqlSessionFactory
- SqlSessionFactory 一旦被創(chuàng)建就應(yīng)該在應(yīng)用的運行期間一直存在,沒有任何理由對它進行清除或重建笨奠。使用 SqlSessionFactory 的最佳實踐是在應(yīng)用運行期間不要重復(fù)創(chuàng)建多次袭蝗,多次重建 SqlSessionFactory 被視為一種代碼“壞味道(bad smell)”唤殴。因此 SqlSessionFactory 的最佳范圍是應(yīng)用范圍。有很多方法可以做到到腥,最簡單的就是使用單例模式或者靜態(tài)單例模式朵逝。
2.4.3 SqlSession
- 每個線程都應(yīng)該有它自己的 SqlSession 實例。SqlSession 的實例不是線程安全的乡范,因此是不能被共享的配名,所以它的最佳的范圍是請求或方法范圍。絕對不能將 SqlSession 實例的引用放在一個類的靜態(tài)域晋辆,甚至一個類的實例變量也不行段誊。也絕不能將 SqlSession 實例的引用放在任何類型的管理范圍中,比如 Serlvet 架構(gòu)中的 HttpSession栈拖。如果你現(xiàn)在正在使用一種 Web 框架连舍,要考慮 SqlSession 放在一個和 HTTP 請求對象相似的范圍中。換句話說涩哟,每次收到的 HTTP 請求索赏,就可以打開一個 SqlSession,返回一個響應(yīng)贴彼,就關(guān)閉它潜腻。這個關(guān)閉操作是很重要的,你應(yīng)該把這個關(guān)閉操作放到 finally 塊中以確保每次都能執(zhí)行關(guān)閉器仗。下面的示例就是一個確保 SqlSession 關(guān)閉的標(biāo)準(zhǔn)模式:
SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
- 在你的所有的代碼中一致性地使用這種模式來保證所有數(shù)據(jù)庫資源都能被正確地關(guān)閉融涣。
2.4.4 映射器實例(Mapper Instances)
- 映射器是創(chuàng)建用來綁定映射語句的接口。映射器接口的實例是從 SqlSession 中獲得的精钮。因此從技術(shù)層面講威鹿,映射器實例的最大范圍是和 SqlSession 相同的,因為它們都是從 SqlSession 里被請求的轨香。盡管如此忽你,映射器實例的最佳范圍是方法范圍。也就是說臂容,映射器實例應(yīng)該在調(diào)用它們的方法中被請求科雳,用過之后即可廢棄。并不需要顯式地關(guān)閉映射器實例脓杉,盡管在整個請求范圍(request scope)保持映射器實例也不會有什么問題糟秘,但是很快你會發(fā)現(xiàn),像 SqlSession 一樣球散,在這個范圍上管理太多的資源的話會難于控制尿赚。所以要保持簡單,最好把映射器放在方法范圍(method scope)內(nèi)。下面的示例就展示了這個實踐:
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// do work
} finally {
session.close();
}
3. XML 映射配置文件
3.1 properties
3.2 settings
3.3 typeAliases
3.4 typeHandlers
3.5 對象工廠(objectFactory)
3.6 插件(plugins)
3.7 配置環(huán)境(environments)
3.8 databaseIdProvider
3.9 映射器(mappers)
- MyBatis 的配置文件包含了影響 MyBatis 行為甚深的設(shè)置(settings)和屬性(properties)信息吼畏。
3.1 properties
- 這些屬性都是可外部配置且可動態(tài)替換的督赤,既可以在典型的 Java 屬性文件中配置,亦可通過 properties 元素的子元素來傳遞泻蚊。例如:
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
- 其中的屬性就可以在整個配置文件中使用來替換需要動態(tài)配置的屬性值躲舌。比如:
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
這個例子中的 username 和 password 將會由 properties 元素中設(shè)置的相應(yīng)值來替換。 driver 和 url 屬性將會由 config.properties 文件中對應(yīng)的值來替換性雄。這樣就為配置提供了諸多靈活選擇没卸。
屬性也可以被傳遞到 SqlSessionBuilder.build()方法中。例如:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, props);
// ... or ...
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, props);
- 如果屬性在不只一個地方進行了配置秒旋,那么 MyBatis 將按照下面的順序來加載:
- 在 properties 元素體內(nèi)指定的屬性首先被讀取约计。
- 然后根據(jù) properties 元素中的 resource 屬性讀取類路徑下屬性文件或根據(jù) url 屬性指定的路徑讀取屬性文件,并覆蓋已讀取的同名屬性迁筛。
- 最后讀取作為方法參數(shù)傳遞的屬性煤蚌,并覆蓋已讀取的同名屬性。
- 因此细卧,通過方法參數(shù)傳遞的屬性具有最高優(yōu)先級尉桩,resource/url 屬性中指定的配置文件次之,最低優(yōu)先級的是 properties 屬性中指定的屬性贪庙。
3.2 settings
- 這是 MyBatis 中極為重要的調(diào)整設(shè)置蜘犁,它們會改變 MyBatis 的運行時行為。下表描述了設(shè)置中各項的意圖止邮、默認值等这橙。
cacheEnabled
- 該配置影響的所有映射器中配置的緩存的全局開關(guān)。
- true | false
- true
lazyLoadingEnabled
- 延遲加載的全局開關(guān)导披。當(dāng)開啟時屈扎,所有關(guān)聯(lián)對象都會延遲加載。 特定關(guān)聯(lián)關(guān)系中可通過設(shè)置fetchType屬性來覆蓋該項的開關(guān)狀態(tài)盛卡。
- true | false
- false
aggressiveLazyLoading
- 當(dāng)啟用時助隧,對任意延遲屬性的調(diào)用會使帶有延遲加載屬性的對象完整加載筑凫;反之滑沧,每種屬性將會按需加載。
- true | false
- true
multipleResultSetsEnabled
- 是否允許單一語句返回多結(jié)果集(需要兼容驅(qū)動)巍实。
- true | false
- true
useColumnLabel
- 使用列標(biāo)簽代替列名滓技。不同的驅(qū)動在這方面會有不同的表現(xiàn), 具體可參考相關(guān)驅(qū)動文檔或通過測試這兩種不同的模式來觀察所用驅(qū)動的結(jié)果棚潦。
- true | false
- true
useGeneratedKeys
- 允許 JDBC 支持自動生成主鍵令漂,需要驅(qū)動兼容。 如果設(shè)置為 true 則這個設(shè)置強制使用自動生成主鍵,盡管一些驅(qū)動不能兼容但仍可正常工作(比如 Derby)乍狐。
- true | false
- False
autoMappingBehavior
- 指定 MyBatis 應(yīng)如何自動映射列到字段或?qū)傩浴?NONE 表示取消自動映射阱佛;PARTIAL 只會自動映射沒有定義嵌套結(jié)果集映射的結(jié)果集较剃。 FULL 會自動映射任意復(fù)雜的結(jié)果集(無論是否嵌套)。
- NONE, PARTIAL, FULL
- PARTIAL
autoMappingUnknownColumnBehavior
- Specify the behavior when detects an unknown column (or unknown property type) of automatic mapping target.
- NONE: Do nothing
- WARNING: Output warning log (The log level of 'org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' must be set to WARN)
- FAILING: Fail mapping (Throw SqlSessionException)
- NONE, WARNING, FAILING
- NONE
defaultExecutorType
- 配置默認的執(zhí)行器收叶。SIMPLE 就是普通的執(zhí)行器;REUSE 執(zhí)行器會重用預(yù)處理語句(prepared statements)共苛; BATCH 執(zhí)行器將重用語句并執(zhí)行批量更新判没。
- SIMPLE, REUSE, BATCH
- SIMPLE
defaultStatementTimeout
- 設(shè)置超時時間,它決定驅(qū)動等待數(shù)據(jù)庫響應(yīng)的秒數(shù)隅茎。
- Any positive integer
- Not Set (null)
defaultFetchSize
- Sets the driver a hint as to control fetching size for return results. This parameter value can be override by a query setting.
- Any positive integer
- Not Set (null)
safeRowBoundsEnabled
- 允許在嵌套語句中使用分頁(RowBounds)澄峰。 If allow, set the false.
- true | false
- False
safeResultHandlerEnabled
- 允許在嵌套語句中使用分頁(ResultHandler)。 If allow, set the false.
- true | false
- True
mapUnderscoreToCamelCase
- 是否開啟自動駝峰命名規(guī)則(camel case)映射辟犀,即從經(jīng)典數(shù)據(jù)庫列名 A_COLUMN 到經(jīng)典 Java 屬性名 aColumn 的類似映射俏竞。
- true | false
- False
localCacheScope
- MyBatis 利用本地緩存機制(Local Cache)防止循環(huán)引用(circular references)和加速重復(fù)嵌套查詢。 默認值為 SESSION堂竟,這種情況下會緩存一個會話中執(zhí)行的所有查詢胞此。 若設(shè)置值為 STATEMENT,本地會話僅用在語句執(zhí)行上跃捣,對相同 SqlSession 的不同調(diào)用將不會共享數(shù)據(jù)漱牵。
- SESSION | STATEMENT
- SESSION
jdbcTypeForNull
- 當(dāng)沒有為參數(shù)提供特定的 JDBC 類型時,為空值指定 JDBC 類型疚漆。 某些驅(qū)動需要指定列的 JDBC 類型酣胀,多數(shù)情況直接用一般類型即可,比如 NULL娶聘、VARCHAR 或 OTHER闻镶。
- JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER
- OTHER
lazyLoadTriggerMethods
- 指定哪個對象的方法觸發(fā)一次延遲加載。
- A method name list separated by commas
- equals,clone,hashCode,toString
defaultScriptingLanguage
- 指定動態(tài) SQL 生成的默認語言丸升。
- A type alias or fully qualified class name.
- org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver
callSettersOnNulls
- 指定當(dāng)結(jié)果集中值為 null 的時候是否調(diào)用映射對象的 setter(map 對象時為 put)方法铆农,這對于有 Map.keySet() 依賴或 null 值初始化的時候是有用的。注意基本類型(int狡耻、boolean等)是不能設(shè)置成 null 的墩剖。
- true | false
- false
logPrefix
- 指定 MyBatis 增加到日志名稱的前綴。
- Any String
- Not set
logImpl
- 指定 MyBatis 所用日志的具體實現(xiàn)夷狰,未指定時將自動查找岭皂。
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
Not set
proxyFactory
- 指定 Mybatis 創(chuàng)建具有延遲加載能力的對象所用到的代理工具。
- CGLIB | JAVASSIST
- JAVASSIST (MyBatis 3.3 or above)
vfsImpl
- Specifies VFS implementations
- Fully qualified class names of custom VFS implementation separated by commas.
- Not set
useActualParamName
- Allow referencing statement parameters by their actual names declared in the method signature. To use this feature, your project must be compiled in Java 8 with -parameters option. (Since: 3.4.1)
- true | false
- true
- 一個配置完整的 settings 元素的示例如下:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
3.3 typeAliases
- 類型別名是為 Java 類型設(shè)置一個短的名字沼头。它只和 XML 配置有關(guān)爷绘,存在的意義僅在于用來減少類完全限定名的冗余书劝。例如:
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
當(dāng)這樣配置時,Blog可以用在任何使用domain.blog.Blog的地方土至。
也可以指定一個包名购对,MyBatis 會在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
- 每一個在包 domain.blog 中的 Java Bean陶因,在沒有注解的情況下洞斯,會使用 Bean 的首字母小寫的非限定類名來作為它的別名。 比如 domain.blog.Author 的別名為 author坑赡;若有注解烙如,則別名為其注解值∫惴瘢看下面的例子:
@Alias("author")
public class Author {
...
}
已經(jīng)為許多常見的 Java 類型內(nèi)建了相應(yīng)的類型別名亚铁。它們都是大小寫不敏感的,需要注意的是由基本類型名稱重復(fù)導(dǎo)致的特殊處理螟加。
別名 映射的類型
_byte <----> byte
_long <----> long
_short <----> short
_int <----> int
_integer <----> int
_double <----> double
_float <----> float
_boolean <----> boolean
string <----> String
byte <----> Byte
long <----> Long
short <----> Short
int <----> Integer
integer <----> Integer
double <----> Double
float <----> Float
boolean <----> Boolean
date <----> Date
decimal <----> BigDecimal
bigdecimal <----> BigDecimal
object <----> Object
map <----> Map
hashmap <----> HashMap
list <----> List
arraylist <----> ArrayList
collection <----> Collection
iterator <----> Iterator
3.4 typeHandlers
- 無論是 MyBatis 在預(yù)處理語句(PreparedStatement)中設(shè)置一個參數(shù)時徘溢,還是從結(jié)果集中取出一個值時, 都會用類型處理器將獲取的值以合適的方式轉(zhuǎn)換成 Java 類型捆探。下表描述了一些默認的類型處理器然爆。
NOTE If you use classes provided by JSR-310(Date and Time API), you can use the mybatis-typehandlers-jsr310.
--
BooleanTypeHandler
- java.lang.Boolean, boolean
- 數(shù)據(jù)庫兼容的 BOOLEAN
ByteTypeHandler
- java.lang.Byte, byte
- 數(shù)據(jù)庫兼容的 NUMERIC 或 BYTE
ShortTypeHandler
- java.lang.Short, short
- 數(shù)據(jù)庫兼容的 NUMERIC 或 SHORT INTEGER
IntegerTypeHandler
- java.lang.Integer, int
- 數(shù)據(jù)庫兼容的 NUMERIC 或 INTEGER
LongTypeHandler
- java.lang.Long, long
- 數(shù)據(jù)庫兼容的 NUMERIC 或 LONG INTEGER
FloatTypeHandler
- java.lang.Float, float
- 數(shù)據(jù)庫兼容的 NUMERIC 或 FLOAT
DoubleTypeHandler
- java.lang.Double, double
- 數(shù)據(jù)庫兼容的 NUMERIC 或 DOUBLE
BigDecimalTypeHandler
- java.math.BigDecimal
- 數(shù)據(jù)庫兼容的 NUMERIC 或 DECIMAL
StringTypeHandler
- java.lang.String
- CHAR, VARCHAR
ClobReaderTypeHandler
- java.io.Reader
ClobTypeHandler
- java.lang.String
- CLOB, LONGVARCHAR
NStringTypeHandler
- java.lang.String
- NVARCHAR, NCHAR
NClobTypeHandler
- java.lang.String
- NCLOB
BlobInputStreamTypeHandler
- java.io.InputStream
ByteArrayTypeHandler
- byte[]
- 數(shù)據(jù)庫兼容的字節(jié)流類型
BlobTypeHandler
- byte[]
- BLOB, LONGVARBINARY
DateTypeHandler
- java.util.Date
- TIMESTAMP
DateOnlyTypeHandler
- java.util.Date
- DATE
TimeOnlyTypeHandler
- java.util.Date
- TIME
SqlTimestampTypeHandler
- java.sql.Timestamp
- TIMESTAMP
SqlDateTypeHandler
- java.sql.Date
- DATE
SqlTimeTypeHandler
- java.sql.Time
- TIME
ObjectTypeHandler
- Any
- OTHER 或未指定類型
EnumTypeHandler
- Enumeration Type
- VARCHAR-任何兼容的字符串類型,存儲枚舉的名稱(而不是索引)
EnumOrdinalTypeHandler
- Enumeration Type
- 任何兼容的 NUMERIC 或 DOUBLE 類型黍图,存儲枚舉的索引(而不是名稱)曾雕。
- 你可以重寫類型處理器或創(chuàng)建你自己的類型處理器來處理不支持的或非標(biāo)準(zhǔn)的類型。 具體做法為:實現(xiàn) org.apache.ibatis.type.TypeHandler 接口助被, 或繼承一個很便利的類 org.apache.ibatis.type.BaseTypeHandler剖张, 然后可以選擇性地將它映射到一個 JDBC 類型。比如:
// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
使用這個的類型處理器將會覆蓋已經(jīng)存在的處理 Java 的 String 類型屬性和 VARCHAR 參數(shù)及結(jié)果的類型處理器揩环。 要注意 MyBatis 不會窺探數(shù)據(jù)庫元信息來決定使用哪種類型搔弄,所以你必須在參數(shù)和結(jié)果映射中指明那是 VARCHAR 類型的字段, 以使其能夠綁定到正確的類型處理器上丰滑。 這是因為:MyBatis 直到語句被執(zhí)行才清楚數(shù)據(jù)類型顾犹。
-
通過類型處理器的泛型,MyBatis 可以得知該類型處理器處理的 Java 類型褒墨,不過這種行為可以通過兩種方法改變:
- 在類型處理器的配置元素(typeHandler element)上增加一個 javaType 屬性(比如:javaType="String")炫刷;
- 在類型處理器的類上(TypeHandler class)增加一個 @MappedTypes 注解來指定與其關(guān)聯(lián)的 Java 類型列表。 如果在 javaType 屬性中也同時指定貌亭,則注解方式將被忽略柬唯。
-
可以通過兩種方式來指定被關(guān)聯(lián)的 JDBC 類型:
- 在類型處理器的配置元素上增加一個 jdbcType 屬性(比如:jdbcType="VARCHAR");
- 在類型處理器的類上(TypeHandler class)增加一個 @MappedJdbcTypes 注解來指定與其關(guān)聯(lián)的 JDBC 類型列表圃庭。 如果在 jdbcType 屬性中也同時指定,則注解方式將被忽略。
When deciding which TypeHandler to use in a ResultMap, the Java type is known (from the result type), but the JDBC type is unknown. MyBatis therefore uses the combination javaType=[TheJavaType], jdbcType=null to choose a TypeHandler. This means that using a @MappedJdbcTypes annotation restricts the scope of a TypeHandler and makes it unavailable for use in ResultMaps unless explicity set. To make a TypeHandler available for use in a ResultMap, set includeNullJdbcType=true on the @MappedJdbcTypes annotation. Since Mybatis 3.4.0 however, if a single TypeHandler is registered to handle a Java type, it will be used by default in ResultMaps using this Java type (i.e. even without includeNullJdbcType=true).
最后剧腻,可以讓 MyBatis 為你查找類型處理器:
<!-- mybatis-config.xml -->
<typeHandlers>
<package name="org.mybatis.example"/>
</typeHandlers>
注意在使用自動檢索(autodiscovery)功能的時候拘央,只能通過注解方式來指定 JDBC 的類型。
你能創(chuàng)建一個泛型類型處理器书在,它可以處理多于一個類灰伟。為達到此目的, 需要增加一個接收該類作為參數(shù)的構(gòu)造器儒旬,這樣在構(gòu)造一個類型處理器的時候 MyBatis 就會傳入一個具體的類栏账。
//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {
private Class<E> type;
public GenericTypeHandler(Class<E> type) {
if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
this.type = type;
}
...
- EnumTypeHandler 和 EnumOrdinalTypeHandler 都是泛型類型處理器(generic TypeHandlers), 我們將會在接下來的部分詳細探討栈源。
3.5 處理枚舉類型
若想映射枚舉類型 Enum挡爵,則需要從 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中選一個來使用。
比如說我們想存儲取近似值時用到的舍入模式甚垦。默認情況下茶鹃,MyBatis 會利用 EnumTypeHandler 來把 Enum 值轉(zhuǎn)換成對應(yīng)的名字。
注意 EnumTypeHandler 在某種意義上來說是比較特別的艰亮,其他的處理器只針對某個特定的類闭翩,而它不同,它會處理任意繼承了 Enum 的類迄埃。
不過疗韵,我們可能不想存儲名字,相反我們的 DBA 會堅持使用整形值代碼侄非。那也一樣輕而易舉: 在配置文件中把 EnumOrdinalTypeHandler 加到 typeHandlers 中即可伶棒, 這樣每個 RoundingMode 將通過他們的序數(shù)值來映射成對應(yīng)的整形。
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
</typeHandlers>
但是怎樣能將同樣的 Enum 既映射成字符串又映射成整形呢彩库?
自動映射器(auto-mapper)會自動地選用 EnumOrdinalTypeHandler 來處理肤无, 所以如果我們想用普通的 EnumTypeHandler,就非要為那些 SQL 語句顯式地設(shè)置要用到的類型處理器不可骇钦。
(下一節(jié)才開始講映射器文件宛渐,所以如果是首次閱讀該文檔,你可能需要先越過這一步眯搭,過會再來看窥翩。)
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="funkyNumber" property="funkyNumber"/>
<result column="roundingMode" property="roundingMode"/>
</resultMap>
<select id="getUser" resultMap="usermap">
select * from users
</select>
<insert id="insert">
insert into users (id, name, funkyNumber, roundingMode) values (
#{id}, #{name}, #{funkyNumber}, #{roundingMode}
)
</insert>
<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap2">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="funkyNumber" property="funkyNumber"/>
<result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
</resultMap>
<select id="getUser2" resultMap="usermap2">
select * from users2
</select>
<insert id="insert2">
insert into users2 (id, name, funkyNumber, roundingMode) values (
#{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
)
</insert>
</mapper>
- 注意,這里的 select 語句強制使用 resultMap 來代替 resultType鳞仙。
3.6 對象工廠(objectFactory)
- MyBatis 每次創(chuàng)建結(jié)果對象的新實例時寇蚊,它都會使用一個對象工廠(ObjectFactory)實例來完成。 默認的對象工廠需要做的僅僅是實例化目標(biāo)類棍好,要么通過默認構(gòu)造方法仗岸,要么在參數(shù)映射存在的時候通過參數(shù)構(gòu)造方法來實例化允耿。 如果想覆蓋對象工廠的默認行為,則可以通過創(chuàng)建自己的對象工廠來實現(xiàn)扒怖。比如:
// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
public Object create(Class type) {
return super.create(type);
}
public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
return super.create(type, constructorArgTypes, constructorArgs);
}
public void setProperties(Properties properties) {
super.setProperties(properties);
}
public <T> boolean isCollection(Class<T> type) {
return Collection.class.isAssignableFrom(type);
}}
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
- ObjectFactory 接口很簡單较锡,它包含兩個創(chuàng)建用的方法,一個是處理默認構(gòu)造方法的盗痒,另外一個是處理帶參數(shù)的構(gòu)造方法的蚂蕴。 最后,setProperties 方法可以被用來配置 ObjectFactory俯邓,在初始化你的 ObjectFactory 實例后骡楼, objectFactory 元素體中定義的屬性會被傳遞給 setProperties 方法。
3.7 插件(plugins)
-
MyBatis 允許你在已映射語句執(zhí)行過程中的某一點進行攔截調(diào)用稽鞭。默認情況下鸟整,MyBatis 允許使用插件來攔截的方法調(diào)用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
這些類中方法的細節(jié)可以通過查看每個方法的簽名來發(fā)現(xiàn),或者直接查看 MyBatis 的發(fā)行包中的源代碼川慌。 假設(shè)你想做的不僅僅是監(jiān)控方法的調(diào)用吃嘿,那么你應(yīng)該很好的了解正在重寫的方法的行為。 因為如果在試圖修改或重寫已有方法的行為的時候梦重,你很可能在破壞 MyBatis 的核心模塊兑燥。 這些都是更低層的類和方法,所以使用插件的時候要特別當(dāng)心琴拧。
通過 MyBatis 提供的強大機制降瞳,使用插件是非常簡單的,只需實現(xiàn) Interceptor 接口蚓胸,并指定了想要攔截的方法簽名即可挣饥。
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
- 上面的插件將會攔截在 Executor 實例中所有的 “update” 方法調(diào)用, 這里的 Executor 是負責(zé)執(zhí)行低層映射語句的內(nèi)部對象沛膳。
NOTE 覆蓋配置類
除了用插件來修改 MyBatis 核心行為之外扔枫,還可以通過完全覆蓋配置類來達到目的。只需繼承后覆蓋其中的每個方法锹安,再把它傳遞到 sqlSessionFactoryBuilder.build(myConfig) 方法即可短荐。再次重申,這可能會嚴(yán)重影響 MyBatis 的行為,務(wù)請慎之又慎。
3.8 配置環(huán)境(environments)
MyBatis 可以配置成適應(yīng)多種環(huán)境俗或,這種機制有助于將 SQL 映射應(yīng)用于多種數(shù)據(jù)庫之中, 現(xiàn)實情況下有多種理由需要這么做糠排。例如,開發(fā)超升、測試和生產(chǎn)環(huán)境需要有不同的配置入宦;或者共享相同 Schema 的多個生產(chǎn)數(shù)據(jù)庫哺徊, 想使用相同的 SQL 映射。許多類似的用例云石。
不過要記装ぁ:盡管可以配置多個環(huán)境研乒,每個 SqlSessionFactory 實例只能選擇其一汹忠。
所以,如果你想連接兩個數(shù)據(jù)庫雹熬,就需要創(chuàng)建兩個 SqlSessionFactory 實例宽菜,每個數(shù)據(jù)庫對應(yīng)一個。而如果是三個數(shù)據(jù)庫竿报,就需要三個實例铅乡,依此類推,記起來很簡單:
每個數(shù)據(jù)庫對應(yīng)一個 SqlSessionFactory 實例
為了指定創(chuàng)建哪種環(huán)境烈菌,只要將它作為可選的參數(shù)傳遞給 SqlSessionFactoryBuilder 即可阵幸。可以接受環(huán)境配置的兩個方法簽名是:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment,properties);
- 如果忽略了環(huán)境參數(shù)芽世,那么默認環(huán)境將會被加載挚赊,如下所示:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader,properties);
- 環(huán)境元素定義了如何配置環(huán)境。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
-
注意這里的關(guān)鍵點:
- 默認的環(huán)境 ID(比如:default=”development”)济瓢。
- 每個 environment 元素定義的環(huán)境 ID(比如:id=”development”)荠割。
- 事務(wù)管理器的配置(比如:type=”JDBC”)。
- 數(shù)據(jù)源的配置(比如:type=”POOLED”)旺矾。
默認的環(huán)境和環(huán)境 ID 是一目了然的蔑鹦。隨你怎么命名,只要保證默認環(huán)境要匹配其中一個環(huán)境ID箕宙。
3.8.1 事務(wù)管理器(transactionManager)
-
在 MyBatis 中有兩種類型的事務(wù)管理器(也就是 type=”[JDBC|MANAGED]”):
- JDBC – 這個配置就是直接使用了 JDBC 的提交和回滾設(shè)置嚎朽,它依賴于從數(shù)據(jù)源得到的連接來管理事務(wù)范圍。
- MANAGED – 這個配置幾乎沒做什么柬帕。它從來不提交或回滾一個連接哟忍,而是讓容器來管理事務(wù)的整個生命周期(比如 JEE 應(yīng)用服務(wù)器的上下文)。 默認情況下它會關(guān)閉連接雕崩,然而一些容器并不希望這樣魁索,因此需要將 closeConnection 屬性設(shè)置為 false 來阻止它默認的關(guān)閉行為。例如:
<transactionManager type="MANAGED"> <property name="closeConnection" value="false"/> </transactionManager>
NOTE如果你正在使用 Spring + MyBatis盼铁,則沒有必要配置事務(wù)管理器粗蔚, 因為 Spring 模塊會使用自帶的管理器來覆蓋前面的配置。
- 這兩種事務(wù)管理器類型都不需要任何屬性饶火。它們不過是類型別名鹏控,換句話說致扯,你可以使用 TransactionFactory 接口的實現(xiàn)類的完全限定名或類型別名代替它們。
public interface TransactionFactory {
void setProperties(Properties props);
Transaction newTransaction(Connection conn);
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
- 任何在 XML 中配置的屬性在實例化之后將會被傳遞給 setProperties() 方法当辐。你也需要創(chuàng)建一個 Transaction 接口的實現(xiàn)類抖僵,這個接口也很簡單:
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
- 使用這兩個接口,你可以完全自定義 MyBatis 對事務(wù)的處理缘揪。
3.8.2 數(shù)據(jù)源(dataSource)
-
dataSource 元素使用標(biāo)準(zhǔn)的 JDBC 數(shù)據(jù)源接口來配置 JDBC 連接對象的資源耍群。
- 許多 MyBatis 的應(yīng)用程序?qū)词纠械睦觼砼渲脭?shù)據(jù)源。然而它并不是必須的找筝。要知道為了方便使用延遲加載蹈垢,數(shù)據(jù)源才是必須的。
有三種內(nèi)建的數(shù)據(jù)源類型(也就是 type=”[UNPOOLED|POOLED|JNDI]”):
3.8.2.1 UNPOOLED
-
– 這個數(shù)據(jù)源的實現(xiàn)只是每次被請求時打開和關(guān)閉連接袖裕。雖然一點慢曹抬,它對在及時可用連接方面沒有性能要求的簡單應(yīng)用程序是一個很好的選擇。 不同的數(shù)據(jù)庫在這方面表現(xiàn)也是不一樣的急鳄,所以對某些數(shù)據(jù)庫來說使用連接池并不重要谤民,這個配置也是理想的。UNPOOLED 類型的數(shù)據(jù)源僅僅需要配置以下 5 種屬性:
- driver – 這是 JDBC 驅(qū)動的 Java 類的完全限定名(并不是JDBC驅(qū)動中可能包含的數(shù)據(jù)源類)疾宏。
- url – 這是數(shù)據(jù)庫的 JDBC URL 地址张足。
- username – 登錄數(shù)據(jù)庫的用戶名。
- password – 登錄數(shù)據(jù)庫的密碼灾锯。
- defaultTransactionIsolationLevel – 默認的連接事務(wù)隔離級別兢榨。
-
作為可選項,你也可以傳遞屬性給數(shù)據(jù)庫驅(qū)動顺饮。要這樣做吵聪,屬性的前綴為“driver.”,例如:
- driver.encoding=UTF8
這將通過DriverManager.getConnection(url,driverProperties)方法傳遞值為 UTF8 的 encoding 屬性給數(shù)據(jù)庫驅(qū)動兼雄。
3.8.2.2 POOLED
– 這種數(shù)據(jù)源的實現(xiàn)利用“池”的概念將 JDBC 連接對象組織起來吟逝,避免了創(chuàng)建新的連接實例時所必需的初始化和認證時間。 這是一種使得并發(fā) Web 應(yīng)用快速響應(yīng)請求的流行處理方式赦肋。
-
除了上述提到 UNPOOLED 下的屬性外块攒,會有更多屬性用來配置 POOLED 的數(shù)據(jù)源:
- poolMaximumActiveConnections – 在任意時間可以存在的活動(也就是正在使用)連接數(shù)量,默認值:10
- poolMaximumIdleConnections – 任意時間可能存在的空閑連接數(shù)佃乘。
- poolMaximumCheckoutTime – 在被強制返回之前囱井,池中連接被檢出(checked out)時間,默認值:20000 毫秒(即 20 秒)
- poolTimeToWait – 這是一個底層設(shè)置趣避,如果獲取連接花費的相當(dāng)長的時間庞呕,它會給連接池打印狀態(tài)日志并重新嘗試獲取一個連接(避免在誤配置的情況下一直安靜的失敗),默認值:20000 毫秒(即 20 秒)住练。
- poolPingQuery – 發(fā)送到數(shù)據(jù)庫的偵測查詢地啰,用來檢驗連接是否處在正常工作秩序中并準(zhǔn)備接受請求。默認是“NO PING QUERY SET”讲逛,這會導(dǎo)致多數(shù)數(shù)據(jù)庫驅(qū)動失敗時帶有一個恰當(dāng)?shù)腻e誤消息亏吝。
- poolPingEnabled – 是否啟用偵測查詢。若開啟盏混,也必須使用一個可執(zhí)行的 SQL 語句設(shè)置 poolPingQuery 屬性(最好是一個非澄蹬福快的 SQL),默認值:false括饶。
- poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的使用頻度株茶。這可以被設(shè)置成匹配具體的數(shù)據(jù)庫連接超時時間来涨,來避免不必要的偵測图焰,默認值:0(即所有連接每一時刻都被偵測 — 當(dāng)然僅當(dāng) poolPingEnabled 為 true 時適用)。
3.8.2.3 JNDI
-
– 這個數(shù)據(jù)源的實現(xiàn)是為了能在如 EJB 或應(yīng)用服務(wù)器這類容器中使用蹦掐,容器可以集中或在外部配置數(shù)據(jù)源技羔,然后放置一個 JNDI 上下文的引用。這種數(shù)據(jù)源配置只需要兩個屬性:
- initial_context – 這個屬性用來在 InitialContext 中尋找上下文(即卧抗,initialContext.lookup(initial_context))藤滥。這是個可選屬性,如果忽略社裆,那么 data_source 屬性將會直接從 InitialContext 中尋找拙绊。
- data_source – 這是引用數(shù)據(jù)源實例位置的上下文的路徑。提供了 initial_context 配置時會在其返回的上下文中進行查找泳秀,沒有提供時則直接在 InitialContext 中查找标沪。
-
和其他數(shù)據(jù)源配置類似,可以通過添加前綴“env.”直接把屬性傳遞給初始上下文嗜傅。比如:
- env.encoding=UTF8
這就會在初始上下文(InitialContext)實例化時往它的構(gòu)造方法傳遞值為 UTF8 的 encoding 屬性金句。
通過需要實現(xiàn)接口 org.apache.ibatis.datasource.DataSourceFactory , 也可使用任何第三方數(shù)據(jù)源吕嘀,:
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
- org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 可被用作父類來構(gòu)建新的數(shù)據(jù)源適配器违寞,比如下面這段插入 C3P0 數(shù)據(jù)源所必需的代碼:
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {
public C3P0DataSourceFactory() {
this.dataSource = new ComboPooledDataSource();
}
}
- 為了令其工作,為每個需要 MyBatis 調(diào)用的 setter 方法中增加一個屬性偶房。下面是一個可以連接至 PostgreSQL 數(shù)據(jù)庫的例子:
<dataSource type="org.myproject.C3P0DataSourceFactory">
<property name="driver" value="org.postgresql.Driver"/>
<property name="url" value="jdbc:postgresql:mydb"/>
<property name="username" value="postgres"/>
<property name="password" value="root"/>
</dataSource>
3.9 databaseIdProvider
- MyBatis 可以根據(jù)不同的數(shù)據(jù)庫廠商執(zhí)行不同的語句趁曼,這種多廠商的支持是基于映射語句中的 databaseId 屬性。 MyBatis 會加載不帶 databaseId 屬性和帶有匹配當(dāng)前數(shù)據(jù)庫 databaseId 屬性的所有語句棕洋。 如果同時找到帶有 databaseId 和不帶 databaseId 的相同語句挡闰,則后者會被舍棄。 為支持多廠商特性只要像下面這樣在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:
<databaseIdProvider type="DB_VENDOR" />
- 這里的 DB_VENDOR 會通過 DatabaseMetaData#getDatabaseProductName() 返回的字符串進行設(shè)置拍冠。 由于通常情況下這個字符串都非常長而且相同產(chǎn)品的不同版本會返回不同的值尿这,所以最好通過設(shè)置屬性別名來使其變短簇抵,如下:
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
在有 properties 時,DB_VENDOR databaseIdProvider 的將被設(shè)置為第一個能匹配數(shù)據(jù)庫產(chǎn)品名稱的屬性鍵對應(yīng)的值射众,如果沒有匹配的屬性將會設(shè)置為 “null”碟摆。 在這個例子中,如果 getDatabaseProductName() 返回“Oracle (DataDirect)”叨橱,databaseId 將被設(shè)置為“oracle”典蜕。
你可以通過實現(xiàn)接口 org.apache.ibatis.mapping.DatabaseIdProvider 并在 mybatis-config.xml 中注冊來構(gòu)建自己的 DatabaseIdProvider:
public interface DatabaseIdProvider {
void setProperties(Properties p);
String getDatabaseId(DataSource dataSource) throws SQLException;
}
3.10 映射器(mappers)
- 既然 MyBatis 的行為已經(jīng)由上述元素配置完了,我們現(xiàn)在就要定義 SQL 映射語句了罗洗。但是首先我們需要告訴 MyBatis 到哪里去找到這些語句愉舔。 Java 在自動查找這方面沒有提供一個很好的方法,所以最佳的方式是告訴 MyBatis 到哪里去找映射文件伙菜。你可以使用相對于類路徑的資源引用轩缤, 或完全限定資源定位符(包括 file:/// 的 URL),或類名和包名等贩绕。例如:
<!-- Using classpath relative resources -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- Using url fully qualified paths -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- Using mapper interface classes -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- Register all interfaces in a package as mappers -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
- 這些配置會告訴了 MyBatis 去哪里找映射文件火的,剩下的細節(jié)就應(yīng)該是每個 SQL 映射文件了,也就是接下來我們要討論的淑倾。
4.Mapper XML 文件
4.1 select
4.2 insert馏鹤、update、delete
4.3 sql
4.4 Parameters
4.5 Result Maps
4.6 自動映射
4.7 緩存(Cache)
MyBatis 的真正強大在于它的映射語句娇哆,也是它的魔力所在湃累。由于它的異常強大,映射器的 XML 文件就顯得相對簡單碍讨。如果拿它跟具有相同功能的 JDBC 代碼進行對比治力,你會立即發(fā)現(xiàn)省掉了將近 95% 的代碼。MyBatis 就是針對 SQL 構(gòu)建的垄开,并且比普通的方法做的更好琴许。
-
SQL 映射文件有很少的幾個頂級元素(按照它們應(yīng)該被定義的順序):
- cache – 給定命名空間的緩存配置。
- cache-ref – 其他命名空間緩存配置的引用溉躲。
- resultMap – 是最復(fù)雜也是最強大的元素榜田,用來描述如何從數(shù)據(jù)庫結(jié)果集中來加載對象音诈。
parameterMap – 已廢棄锈锤!老式風(fēng)格的參數(shù)映射拳锚。內(nèi)聯(lián)參數(shù)是首選,這個元素可能在將來被移除捐名,這里不會記錄骨望。- sql – 可被其他語句引用的可重用語句塊削罩。
- insert – 映射插入語句
- update – 映射更新語句
- delete – 映射刪除語句
- select – 映射查詢語句
下一部分將從語句本身開始來描述每個元素的細節(jié)靴寂。
4.1 select
- 查詢語句是 MyBatis 中最常用的元素之一帕胆,光能把數(shù)據(jù)存到數(shù)據(jù)庫中價值并不大,如果還能重新取出來才有用废亭,多數(shù)應(yīng)用也都是查詢比修改要頻繁国章。對每個插入、更新或刪除操作豆村,通常對應(yīng)多個查詢操作液兽。這是 MyBatis 的基本原則之一,也是將焦點和努力放到查詢和結(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)然,這需要很多單獨的 JDBC 的代碼來提取結(jié)果并將它們映射到對象實例中嗅绸,這就是 MyBatis 節(jié)省你時間的地方。我們需要深入了解參數(shù)和結(jié)果映射撕彤,細節(jié)部分我們下面來了解鱼鸠。
select 元素有很多屬性允許你配置,來決定每條語句的作用細節(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ù)類的完全限定名或別名职员。這個屬性是可選的麻蹋,因為 MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的參數(shù),默認值為 unset焊切。
parameterMap : 這是引用外部 parameterMap 的已經(jīng)被廢棄的方法扮授。使用內(nèi)聯(lián)參數(shù)映射和 parameterType 屬性。
resultType : 從這條語句中返回的期望類型的類的完全限定名或別名专肪。注意如果是集合情形刹勃,那應(yīng)該是集合可以包含的類型,而不能是集合本身嚎尤。使用 resultType 或 resultMap荔仁,但不能同時使用。
resultMap : 外部 resultMap 的命名引用。結(jié)果集的映射是 MyBatis 最強大的特性乏梁,對其有一個很好的理解的話次洼,許多復(fù)雜映射的情形都能迎刃而解。使用 resultMap 或 resultType遇骑,但不能同時使用滓玖。
flushCache : 將其設(shè)置為 true,任何時候只要語句被調(diào)用质蕉,都會導(dǎo)致本地緩存和二級緩存都會被清空势篡,默認值:false。
useCache : 將其設(shè)置為 true模暗,將會導(dǎo)致本條語句的結(jié)果被二級緩存禁悠,默認值:對 select 元素為 true。
timeout : 這個設(shè)置是在拋出異常之前兑宇,驅(qū)動程序等待數(shù)據(jù)庫返回請求結(jié)果的秒數(shù)碍侦。默認值為 unset(依賴驅(qū)動)。
fetchSize : 這是嘗試影響驅(qū)動程序每次批量返回的結(jié)果行數(shù)和這個設(shè)置值相等隶糕。默認值為 unset(依賴驅(qū)動)瓷产。
statementType : STATEMENT,PREPARED 或 CALLABLE 的一個枚驻。這會讓 MyBatis 分別使用 Statement濒旦,PreparedStatement 或 CallableStatement,默認值:PREPARED再登。
resultSetType : FORWARD_ONLY尔邓,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一個,默認值為 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)存不夠用壹士。默認值:false磷雇。
resultSets : 這個設(shè)置僅對多結(jié)果集的情況適用,它將列出語句執(zhí)行后返回的結(jié)果集并每個結(jié)果集給一個名稱躏救,名稱是逗號分隔的唯笙。
4.2 insert, update 和 delete
- 數(shù)據(jù)變更語句 insert螟蒸,update 和 delete 的實現(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ù)的完全限定類名或別名七嫌。這個屬性是可選的,因為 MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的參數(shù)苞慢,默認值為 unset诵原。
parameterMap : 這是引用外部 parameterMap 的已經(jīng)被廢棄的方法。使用內(nèi)聯(lián)參數(shù)映射和 parameterType 屬性挽放。
flushCache : 將其設(shè)置為 true绍赛,任何時候只要語句被調(diào)用,都會導(dǎo)致本地緩存和二級緩存都會被清空辑畦,默認值:true(對應(yīng)插入吗蚌、更新和刪除語句)。
timeout : 這個設(shè)置是在拋出異常之前纯出,驅(qū)動程序等待數(shù)據(jù)庫返回請求結(jié)果的秒數(shù)蚯妇。默認值為 unset(依賴驅(qū)動)。
statementType : STATEMENT暂筝,PREPARED 或 CALLABLE 的一個箩言。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement焕襟,默認值:PREPARED陨收。
useGeneratedKeys : (僅對 insert 和 update 有用)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數(shù)據(jù)庫內(nèi)部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關(guān)系數(shù)據(jù)庫管理系統(tǒng)的自動遞增字段),默認值:false胧洒。
keyProperty : (僅對 insert 和 update 有用)唯一標(biāo)記一個屬性畏吓,MyBatis 會通過 getGeneratedKeys 的返回值或者通過 insert 語句的 selectKey 子元素設(shè)置它的鍵值,默認: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>
- If your database also supports multi-row insert, you can pass a list or an array of Authors and retrieve the auto-generated keys.
<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>
對于不支持自動生成類型的數(shù)據(jù)庫或可能不支持自動生成主鍵 JDBC 驅(qū)動來說,MyBatis 有另外一種方法來生成主鍵宏娄。
這里有一個簡單(甚至很傻)的示例问裕,它可以生成一個隨機 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 元素將會首先運行僻澎,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 類型拼苍。
4.3 sql
- 這個元素可以被用來定義可重用的 SQL 代碼段笑诅,可以包含在其他語句中。它可以被靜態(tài)地(在加載參數(shù)) 參數(shù)化. 不同的屬性值通過包含的實例變化. 比如:
<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>
- 屬性值可以用于包含的refid屬性或者包含的字句里面的屬性值吆你,例如:
<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>
4.4 參數(shù)(Parameters)
- 前面的所有語句中你所見到的都是簡單參數(shù)的例子,實際上參數(shù)是 MyBatis 非常強大的元素俊犯,對于簡單的做法妇多,大概 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ù)類型(比如整型和字符串)因為沒有相關(guān)屬性绢彤,它會完全用參數(shù)值來替代七问。然而,如果傳入一個復(fù)雜的對象茫舶,行為就會有一點不同了械巡。比如:
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
如果 User 類型的參數(shù)對象傳遞到了語句中,id饶氏、username 和 password 屬性將會被查找讥耗,然后將它們的值傳入預(yù)處理語句的參數(shù)中。
這點對于向語句中傳參是比較好的而且又簡單疹启,不過參數(shù)映射的功能遠不止于此古程。
首先,像 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ù)。
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
- 最后赏僧,mode 屬性允許你指定 IN大猛,OUT 或 INOUT 參數(shù)。如果參數(shù)為 OUT 或 INOUT淀零,參數(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ù)時你必須告訴它語句類型名稱。比如(再次提示共啃,在實際中要像這樣不能換行):
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}
- 盡管所有這些強大的選項很多時候你只簡單指定屬性名占调,其他的事情 MyBatis 會自己去推斷,最多你需要為可能為空的列名指定 jdbcType移剪。
#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}
字符串替換
- 默認情況下,使用#{}格式的語法會導(dǎo)致 MyBatis 創(chuàng)建預(yù)處理語句屬性并安全地設(shè)置值(比如?)究珊。這樣做更安全,更迅速纵苛,通常也是首選做法剿涮,不過有時你只是想直接在 SQL 語句中插入一個不改變的字符串。比如攻人,像 ORDER BY取试,你可以這樣來使用:
ORDER BY ${columnName}
- 這里 MyBatis 不會修改或轉(zhuǎn)義字符串。
NOTE 以這種方式接受從用戶輸出的內(nèi)容并提供給語句中不變的字符串是不安全的怀吻,會導(dǎo)致潛在的 SQL 注入攻擊瞬浓,因此要么不允許用戶輸入這些字段,要么自行轉(zhuǎn)義并檢驗蓬坡。
4.5 Result Maps
resultMap 元素是 MyBatis 中最重要最強大的元素猿棉。它就是讓你遠離 90%的需要從結(jié)果 集中取出數(shù)據(jù)的 JDBC 代碼的那個東西, 而且在一些情形下允許你做一些 JDBC 不支持的事 情磅叛。 事實上, 編寫相似于對復(fù)雜語句聯(lián)合映射這些等同的代碼, 也許可以跨過上千行的代碼。 ResultMap 的設(shè)計就是簡單語句不需要明確的結(jié)果映射,而很多復(fù)雜語句確實需要描述它們 的關(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="user_name"/>
<result property="password" column="hashed_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>
- 如果世界總是這么簡單就好了踱蠢。
4.5.1 高級結(jié)果映射
MyBatis 創(chuàng)建的一個想法:數(shù)據(jù)庫不用永遠是你想要的或需要它們是什么樣的火欧。而我們 最喜歡的數(shù)據(jù)庫最好是第三范式或 BCNF 模式,但它們有時不是。如果可能有一個單獨的 數(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)天最初它看起來令人生畏,但實際上非常簡單。
<!-- 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 元素的概念視圖
4.5.2 resultMap
constructor - 類在實例化時,用來注入結(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
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.
--
最佳實踐 通常逐步建立結(jié)果映射。單元測試的真正幫助在這里抖坪。如果你嘗試創(chuàng)建 一次創(chuàng)建一個向上面示例那樣的巨大的結(jié)果映射, 那么可能會有錯誤而且很難去控制它 來工作萍鲸。開始簡單一些,一步一步的發(fā)展。而且要進行單元測試!使用該框架的缺點是 它們有時是黑盒(是否可見源代碼) 擦俐。你確定你實現(xiàn)想要的行為的最好選擇是編寫單元 測試猿推。它也可以你幫助得到提交時的錯誤。
- 下面一部分將詳細說明每個元素。
4.5.3 id & result
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
這些是結(jié)果映射最基本內(nèi)容蹬叭。id 和 result 都映射一個單獨列的值到簡單數(shù)據(jù)類型(字符 串,整型,雙精度浮點數(shù),日期等)的單獨屬性或字段。
這兩者之間的唯一不同是 id 表示的結(jié)果將是當(dāng)比較對象實例時用到的標(biāo)識屬性状知。這幫 助來改進整體表現(xiàn),特別是緩存和嵌入結(jié)果映射(也就是聯(lián)合映射) 秽五。
每個都有一些屬性:
Id and Result Attributes
property : 映射到列結(jié)果的字段或?qū)傩浴H绻ヅ涞氖谴嬖诘?和給定名稱相同 的 JavaBeans 的屬性,那么就會使用饥悴。否則 MyBatis 將會尋找給定名稱 property 的字段坦喘。這兩種情形你可以使用通常點式的復(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 類型是僅 僅需要對插入,更新和刪除操作可能為空的列進行處理。這是 JDBC jdbcType 的需要,而不是 MyBatis 的印屁。如果你直接使用 JDBC 編程,你需要指定 這個類型-但僅僅對可能為空的值循捺。
typeHandler : 我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默 認的類型處理器雄人。這個屬性值是類的完全限定名或者是一個類型處理 器的實現(xiàn),或者是類型別名从橘。
4.5.4 支持的 JDBC 類型
- 為了未來的參考,MyBatis 通過包含的 jdbcType 枚舉型,支持下面的 JDBC 類型。
BITFLOAT 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
4.5.5 構(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 屬 性來達到這個目的,但是一些人更青睞構(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 類型是僅僅 需要對插入, 更新和刪除操作可能為空的列進行處理蝗柔。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的闻葵。如果你直接使用
JDBC 編程,你需要指定這個類型-但 僅僅對可能為空的值。
typeHandler : 我們在前面討論過默認的類型處理器癣丧。使用這個屬性,你可以覆蓋默認的 類型處理器槽畔。 這個屬性值是類的完全限定名或者是一個類型處理器的實現(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.
4.5.6 關(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ū)傩园ぞ觥H绻ヅ涞氖谴嬖诘?和給定名稱相同的 property JavaBeans 的屬性, 那么就會使用弥喉。 否則
MyBatis 將會尋找給定名稱的字段允青。 這兩種情形你可以使用通常點式的復(fù)雜屬性導(dǎo)航。比如,你可以這樣映射 一 些 東 西 :“ username ”, 或 者 映 射 到 一 些 復(fù) 雜 的 東 西 : “address.street.number” 蒋得。
javaType 一個 Java 類的完全限定名,或一個類型別名(參考上面內(nèi)建類型別名的列 表) 级及。如果你映射到一個 JavaBean,MyBatis 通常可以斷定類型额衙。然而,如 javaType 果你映射到的是 HashMap,那么你應(yīng)該明確地指定 javaType 來保證所需的 行為饮焦。
jdbcType 在這個表格之前的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅僅 需要對插入, 更新和刪除操作可能為空的列進行處理窍侧。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的县踢。如果你直接使用 JDBC 編程,你需要指定這個類型-但 僅僅對可能為空的值。
typeHandler 我們在前面討論過默認的類型處理器伟件。使用這個屬性,你可以覆蓋默認的 typeHandler 類型處理器硼啤。 這個屬性值是類的完全限定名或者是一個類型處理器的實現(xiàn), 或者是類型別名。
4.5.6.1 關(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ù)。表格后面 有一個詳細的示例习绢。 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í)行了一個單獨的 SQL 語句來獲取結(jié)果列表(就是“+1”)篮幢。
- 對返回的每條記錄,你執(zhí)行了一個查詢語句來為每個加載細節(jié)(就是“N”)大刊。
這個問題會導(dǎo)致成百上千的 SQL 語句被執(zhí)行。這通常不是期望的三椿。
MyBatis 能延遲加載這樣的查詢就是一個好處,因此你可以分散這些語句同時運行的消 耗。然而,如果你加載一個列表,之后迅速迭代來訪問嵌套的數(shù)據(jù),你會調(diào)用所有的延遲加 載,這樣的行為可能是很糟糕的葫辐。
所以還有另外一種方法搜锰。
4.5.6.2 關(guān)聯(lián)的嵌套結(jié)果
resultMap : 這是結(jié)果映射的 ID,可以映射關(guān)聯(lián)的嵌套結(jié)果到一個合適的對象圖中。這 是一種替代方法來調(diào)用另外一個查詢語句耿战。這允許你聯(lián)合多個表來合成到 resultMap 一個單獨的結(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)合查詢, 以及采取保護來確保所有結(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é)果映射來加載作 者實例晕鹊。
非常重要: 在嵌套據(jù)誒過映射中 id 元素扮演了非常重要的角色松却。應(yīng)應(yīng)該通常指定一個 或多個屬性,它們可以用來唯一標(biāo)識結(jié)果。實際上就是如果你離開她了,但是有一個嚴(yán)重的 性能問題時 MyBatis 仍然可以工作溅话。選擇的屬性越少越好,它們可以唯一地標(biāo)識結(jié)果晓锻。主鍵 就是一個顯而易見的選擇(盡管是聯(lián)合主鍵)。
- 現(xiàn)在,上面的示例用了外部的結(jié)果映射元素來映射關(guān)聯(lián)飞几。這使得 Author 結(jié)果映射可以 重用砚哆。然而,如果你不需要重用它的話,或者你僅僅引用你所有的結(jié)果映射合到一個單獨描 述的結(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)躁锁。但是“有很多個”是怎樣的?下面這 個部分就是來討論這個主題的。
4.5.7 集合
<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)是相同的绪钥。實際上,它們也很相似,文檔的異同是多余的灿里。 所以我們更多關(guān)注于它們的不同。
我們來繼續(xù)上面的示例,一個博客只有一個作者程腹。但是博客有很多文章匣吊。在博客類中, 這可以由下面這樣的寫法來表示:
private List<Post> posts;
- 要映射嵌套結(jié)果集合到 List 中,我們使用集合元素。就像關(guān)聯(lián)元素一樣,我們可以從 連接中使用嵌套查詢,或者嵌套結(jié)果。
4.5.7.1 集合的嵌套查詢
- 首先,讓我們看看使用嵌套查詢來為博客加載文章色鸳。
<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) 該注意我們使用的是集合元素。然后要注意那個新的“ofType”屬性命雀。這個屬性用來區(qū)分 JavaBean(或字段)屬性類型和集合包含的類型來說是很重要的蒜哀。所以你可以讀出下面這個 映射:
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
讀作: “在 Post 類型的 ArrayList 中 posts 的集合±羯埃”
javaType 屬性是不需要的,因為 MyBatis 在很多情況下會為你算出來撵儿。所以你可以縮短 寫法:
<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>
4.5.7.2 集合的嵌套結(jié)果
至此,你可以猜測集合的嵌套結(jié)果是如何來工作的,因為它和關(guān)聯(lián)完全相同,除了它應(yīng) 用了一個“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)簽的簡單映射。現(xiàn) 在用文章映射集合映射博客,可以簡單寫為:
<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 元素的重要性,如果你不記得了,請閱讀上面的關(guān)聯(lián)部分狐血。
同樣, 如果你引用更長的形式允許你的結(jié)果映射的更多重用, 你可以使用下面這個替代 的映射:
<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>
注意 這個對你所映射的內(nèi)容沒有深度,廣度或關(guān)聯(lián)和集合相聯(lián)合的限制淀歇。當(dāng)映射它們 時你應(yīng)該在大腦中保留它們的表現(xiàn)。 你的應(yīng)用在找到最佳方法前要一直進行的單元測試和性 能測試匈织。好在 myBatis 讓你后來可以改變想法,而不對你的代碼造成很小(或任何)影響浪默。
- 高級關(guān)聯(lián)和集合映射是一個深度的主題。文檔只能給你介紹到這了缀匕。加上一點聯(lián)系,你 會很快清楚它們的用法纳决。
4.5.8 鑒別器
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
有時一個單獨的數(shù)據(jù)庫查詢也許返回很多不同 (但是希望有些關(guān)聯(lián)) 數(shù)據(jù)類型的結(jié)果集。 鑒別器元素就是被設(shè)計來處理這個情況的, 還有包括類的繼承層次結(jié)構(gòu)乡小。 鑒別器非常容易理 解,因為它的表現(xiàn)很像 Java 語言中的 switch 語句阔加。
定義鑒別器指定了 column 和 javaType 屬性。 列是 MyBatis 查找比較值的地方劲件。 JavaType 是需要被用來保證等價測試的合適類型(盡管字符串在很多情形下都會有用)掸哑。比如:
<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
<case value="3" resultMap="vanResult"/>
<case value="4" resultMap="suvResult"/>
</discriminator>
</resultMap>
- 在這個示例中, MyBatis 會從結(jié)果集中得到每條記錄, 然后比較它的 vehicle 類型的值。 如果它匹配任何一個鑒別器的實例,那么就使用這個實例指定的結(jié)果映射零远。換句話說,這樣 做完全是剩余的結(jié)果映射被忽略(除非它被擴展,這在第二個示例中討論) 苗分。如果沒有任何 一個實例相匹配,那么 MyBatis 僅僅使用鑒別器塊外定義的結(jié)果映射。所以,如果 carResult 按如下聲明:
<resultMap id="carResult" type="Car">
<result property="doorCount" column="door_count" />
</resultMap>
- 那么只有 doorCount 屬性會被加載牵辣。這步完成后完整地允許鑒別器實例的獨立組,盡管 和父結(jié)果映射可能沒有什么關(guān)系摔癣。這種情況下,我們當(dāng)然知道 cars 和 vehicles 之間有關(guān)系, 如 Car 是一個 Vehicle 實例。因此,我們想要剩余的屬性也被加載纬向。我們設(shè)置的結(jié)果映射的 簡單改變?nèi)缦隆?/li>
<resultMap id="carResult" type="Car" extends="vehicleResult">
<result property="doorCount" column="door_count" />
</resultMap>
現(xiàn)在 vehicleResult 和 carResult 的屬性都會被加載了择浊。
盡管曾經(jīng)有些人會發(fā)現(xià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 將會為你自動匹配列 和屬性琢岩。所以這些例子中的大部分是很冗長的,而其實是不需要的。也就是說,很多數(shù)據(jù)庫 是很復(fù)雜的,我們不太可能對所有示例都能依靠它师脂。
4.6 自動映射
正如你在前面一節(jié)看到的担孔,在簡單的場景下江锨,MyBatis可以替你自動映射查詢結(jié)果。 如果遇到復(fù)雜的場景糕篇,你需要構(gòu)建一個result map啄育。 但是在本節(jié)你將看到,你也可以混合使用這兩種策略拌消。 讓我們到深一點的層面上看看自動映射是怎樣工作的挑豌。
當(dāng)自動映射查詢結(jié)果時,MyBatis會獲取sql返回的列名并在java類中查找相同名字的屬性(忽略大小寫)墩崩。 這意味著如果Mybatis發(fā)現(xiàn)了ID列和id屬性氓英,Mybatis會將ID的值賦給id。
通常數(shù)據(jù)庫列使用大寫單詞命名鹦筹,單詞間用下劃線分隔债蓝;而java屬性一般遵循駝峰命名法。 為了在這兩種命名方式之間啟用自動映射盛龄,需要將 mapUnderscoreToCamelCase設(shè)置為true。
自動映射甚至在特定的result map下也能工作芳誓。在這種情況下余舶,對于每一個result map,所有的ResultSet提供的列, 如果沒有被手工映射锹淌,則將被自動映射匿值。自動映射處理完畢后手工映射才會被處理。 在接下來的例子中赂摆, id 和 userName列將被自動映射挟憔, hashed_password 列將根據(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>
- There are three auto-mapping levels:
NONE : disables auto-mapping. Only manually mapped properties will be set.
PARTIAL : will auto-map results except those that have nested result mappings defined inside (joins).
FULL : auto-maps everything.
- The default value is PARTIAL, and it is so for a reason. When FULL is used auto-mapping will be performed when processing join results and joins retrieve data of several different entities in the same row hence this may result in undesired mappings. To understand the risk have a look at the following sample:
<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>
With this result map both Blog and Author will be auto-mapped. But note that Author has an id property and there is a column named id in the ResultSet so Author's id will be filled with Blog's id, and that is not what you were expecting. So use the FULL option with caution.
Regardless of the auto-mapping level configured you can enable or disable the automapping for an specific ResultMap by adding the attribute autoMapping to it:
<resultMap id="userResultMap" type="User" autoMapping="false">
<result property="password" column="hashed_password"/>
</resultMap>
4.7 緩存
MyBatis 包含一個非常強大的查詢緩存特性,它可以非常方便地配置和定制烟号。MyBatis 3 中的緩存實現(xiàn)的很多改進都已經(jīng)實現(xiàn)了,使得它更加強大而且易于配置绊谭。
默認情況下是沒有開啟緩存的,除了局部的 session 緩存,可以增強變現(xiàn)而且處理循環(huán) 依賴也是必須的。要開啟二級緩存,你需要在你的 SQL 映射文件中添加一行:
<cache/>
-
字面上看就是這樣汪拥。這個簡單語句的效果如下:
- 映射語句文件中的所有 select 語句將會被緩存达传。
- 映射語句文件中的所有 insert,update 和 delete 語句會刷新緩存。
- 緩存會使用 Least Recently Used(LRU,最近最少使用的)算法來收回迫筑。
- 根據(jù)時間表(比如 no Flush Interval,沒有刷新間隔), 緩存不會以任何時間順序 來刷新宪赶。
- 緩存會存儲列表集合或?qū)ο?無論查詢方法返回什么)的 1024 個引用。
- 緩存會被視為是 read/write(可讀/可寫)的緩存,意味著對象檢索不是共享的,而 且可以安全地被調(diào)用者修改,而不干擾其他調(diào)用者或線程所做的潛在修改脯燃。
所有的這些屬性都可以通過緩存元素的屬性來修改搂妻。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
這個更高級的配置創(chuàng)建了一個 FIFO 緩存,并每隔 60 秒刷新,存數(shù)結(jié)果對象或列表的 512 個引用,而且返回的對象被認為是只讀的,因此在不同線程中的調(diào)用者之間修改它們會 導(dǎo)致沖突。
-
可用的收回策略有:
- LRU – 最近最少使用的:移除最長時間不被使用的對象辕棚。
- FIFO – 先進先出:按對象進入緩存的順序來移除它們欲主。
- SOFT – 軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象邓厕。
- WEAK – 弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象。
-
默認的是 LRU岛蚤。
flushInterval(刷新間隔)可以被設(shè)置為任意的正整數(shù),而且它們代表一個合理的毫秒 形式的時間段邑狸。默認情況是不設(shè)置,也就是沒有刷新間隔,緩存僅僅調(diào)用語句時刷新。
size(引用數(shù)目)可以被設(shè)置為任意正整數(shù),要記住你緩存的對象數(shù)目和你運行環(huán)境的 可用內(nèi)存資源數(shù)目涤妒。默認值是 1024单雾。
readOnly(只讀)屬性可以被設(shè)置為 true 或 false。只讀的緩存會給所有調(diào)用者返回緩 存對象的相同實例她紫。因此這些對象不能被修改硅堆。這提供了很重要的性能優(yōu)勢』叨铮可讀寫的緩存 會返回緩存對象的拷貝(通過序列化) 渐逃。這會慢一些,但是安全,因此默認是 false。
4.7.1 使用自定義緩存
- 除了這些自定義緩存的方式, 你也可以通過實現(xiàn)你自己的緩存或為其他第三方緩存方案 創(chuàng)建適配器來完全覆蓋緩存行為民褂。
<cache type="com.domain.something.MyCustomCache"/>
- 這個示 例展 示了 如何 使用 一個 自定義 的緩 存實 現(xiàn)茄菊。type 屬 性指 定的 類必 須實現(xiàn) org.mybatis.cache.Cache 接口。這個接口是 MyBatis 框架中很多復(fù)雜的接口之一,但是簡單 給定它做什么就行赊堪。
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();
}
- 要配置你的緩存, 簡單和公有的 JavaBeans 屬性來配置你的緩存實現(xiàn), 而且是通過 cache 元素來傳遞屬性, 比如, 下面代碼會在你的緩存實現(xiàn)中調(diào)用一個稱為 “setCacheFile(String file)” 的方法:
<cache type="com.domain.something.MyCustomCache">
<property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
你可以使用所有簡單類型作為 JavaBeans 的屬性,MyBatis 會進行轉(zhuǎn)換面殖。
記得緩存配置和緩存實例是綁定在 SQL 映射文件的命名空間是很重要的。因此,所有 在相同命名空間的語句正如綁定的緩存一樣哭廉。 語句可以修改和緩存交互的方式, 或在語句的 語句的基礎(chǔ)上使用兩種簡單的屬性來完全排除它們脊僚。默認情況下,語句可以這樣來配置:
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
- 因為那些是默認的,你明顯不能明確地以這種方式來配置一條語句。相反,如果你想改 變默認的行為,只能設(shè)置 flushCache 和 useCache 屬性遵绰。比如,在一些情況下你也許想排除 從緩存中查詢特定語句結(jié)果,或者你也許想要一個查詢語句來刷新緩存辽幌。相似地,你也許有 一些更新語句依靠執(zhí)行而不需要刷新緩存。
4.7.2 參照緩存
- 回想一下上一節(jié)內(nèi)容, 這個特殊命名空間的唯一緩存會被使用或者刷新相同命名空間內(nèi) 的語句椿访。也許將來的某個時候,你會想在命名空間中共享相同的緩存配置和實例乌企。在這樣的 情況下你可以使用 cache-ref 元素來引用另外一個緩存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
5. 動態(tài) SQL
MyBatis 的強大特性之一便是它的動態(tài) SQL赎离。如果你有使用 JDBC 或其他類似框架的經(jīng)驗逛犹,你就能體會到根據(jù)不同條件拼接 SQL 語句有多么痛苦。拼接的時候要確保不能忘了必要的空格梁剔,還要注意省掉列名列表最后的逗號虽画。利用動態(tài) SQL 這一特性可以徹底擺脫這種痛苦。
通常使用動態(tài) SQL 不可能是獨立的一部分,MyBatis 當(dāng)然使用一種強大的動態(tài) SQL 語言來改進這種情形,這種語言可以被用在任意的 SQL 映射語句中荣病。
動態(tài) SQL 元素和使用 JSTL 或其他類似基于 XML 的文本處理器相似码撰。在 MyBatis 之前的版本中,有很多的元素需要來了解或舞。MyBatis 3 大大提升了它們,現(xiàn)在用不到原先一半的元素就可以了瘩欺。MyBatis 采用功能強大的基于 OGNL 的表達式來消除其他元素假抄。
5.1 if
- 動態(tài) SQL 通常要做的事情是有條件地包含 where 子句的一部分近上。比如:
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
這條語句提供了一個可選的文本查找類型的功能。如果沒有傳入“title”柴梆,那么所有處于“ACTIVE”狀態(tài)的BLOG都會返回陨溅;反之若傳入了“title”,那么就會把模糊查找“title”內(nèi)容的BLOG結(jié)果返回(就這個例子而言绍在,細心的讀者會發(fā)現(xiàn)其中的參數(shù)值是可以包含一些掩碼或通配符的)门扇。
如果想可選地通過“title”和“author”兩個條件搜索該怎么辦呢?首先偿渡,改變語句的名稱讓它更具實際意義臼寄;然后只要加入另一個條件即可。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
5.2 choose, when, otherwise
有些時候溜宽,我們不想用到所有的條件語句吉拳,而只想從中擇其一二。針對這種情況适揉,MyBatis 提供了 choose 元素留攒,它有點像 Java 中的 switch 語句。
還是上面的例子嫉嘀,但是這次變?yōu)樘峁┝恕皌itle”就按“title”查找稼跳,提供了“author”就按“author”查找,若兩者都沒有提供吃沪,就返回所有符合條件的BLOG(實際情況可能是由管理員按一定策略選出BLOG列表,而不是返回大量無意義的隨機結(jié)果)什猖。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
5.3 trim, where, set
- 前面幾個例子已經(jīng)合宜地解決了一個臭名昭著的動態(tài) SQL 問題∑北耄現(xiàn)在考慮回到“if”示例,這次我們將“ACTIVE = 1”也設(shè)置成動態(tài)的條件不狮,看看會發(fā)生什么降铸。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
- 如果這些條件沒有一個能匹配上將會怎樣?最終這條 SQL 會變成這樣:
SELECT * FROM BLOG
WHERE
- 這會導(dǎo)致查詢失敗摇零。如果僅僅第二個條件匹配又會怎樣推掸?這條 SQL 最終會是這樣:
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
這個查詢也會失敗。這個問題不能簡單的用條件句式來解決驻仅,如果你也曾經(jīng)被迫這樣寫過谅畅,那么你很可能從此以后都不想再這樣去寫了。
MyBatis 有一個簡單的處理噪服,這在90%的情況下都會有用毡泻。而在不能使用的地方,你可以自定義處理方式來令其正常工作粘优。一處簡單的修改就能得到想要的效果:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
where 元素知道只有在一個以上的if條件有值的情況下才去插入“WHERE”子句仇味。而且呻顽,若最后的內(nèi)容是“AND”或“OR”開頭的,where 元素也知道如何將他們?nèi)コ?/p>
如果 where 元素沒有按正常套路出牌丹墨,我們還是可以通過自定義 trim 元素來定制我們想要的功能廊遍。比如,和 where 元素等價的自定義 trim 元素為:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 屬性會忽略通過管道分隔的文本序列(注意此例中的空格也是必要的)贩挣。它帶來的結(jié)果就是所有在 prefixOverrides 屬性中指定的內(nèi)容將被移除喉前,并且插入 prefix 屬性中指定的內(nèi)容。
類似的用于動態(tài)更新語句的解決方案叫做 set揽惹。set 元素可以被用于動態(tài)包含需要更新的列被饿,而舍去其他的。比如:
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
這里搪搏,set 元素會動態(tài)前置 SET 關(guān)鍵字狭握,同時也會消除無關(guān)的逗號,因為用了條件語句之后很可能就會在生成的賦值語句的后面留下這些逗號疯溺。
若你對等價的自定義 trim 元素的樣子感興趣论颅,那這就應(yīng)該是它的真面目:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
- 注意這里我們忽略的是后綴中的值,而又一次附加了前綴中的值囱嫩。
5.4 foreach
- 動態(tài) SQL 的另外一個常用的必要操作是需要對一個集合進行遍歷恃疯,通常是在構(gòu)建 IN 條件語句的時候。比如:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
- foreach 元素的功能是非常強大的墨闲,它允許你指定一個集合今妄,聲明可以用在元素體內(nèi)的集合項和索引變量。它也允許你指定開閉匹配的字符串以及在迭代中間放置分隔符鸳碧。這個元素是很智能的盾鳞,因此它不會偶然地附加多余的分隔符。
注意 你可以將任何可迭代對象(如列表瞻离、集合等)和任何的字典或者數(shù)組對象傳遞給foreach作為集合參數(shù)腾仅。當(dāng)使用可迭代對象或者數(shù)組時,index是當(dāng)前迭代的次數(shù)套利,item的值是本次迭代獲取的元素推励。當(dāng)使用字典(或者Map.Entry對象的集合)時,index是鍵肉迫,item是值验辞。
- 到此我們已經(jīng)完成了涉及 XML 配置文件和 XML 映射文件的討論。下一部分將詳細探討 Java API喊衫,這樣才能從已創(chuàng)建的映射中獲取最大利益受神。
5.6 bind
- bind 元素可以從 OGNL 表達式中創(chuàng)建一個變量并將其綁定到上下文。比如:
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
5.6 Multi-db vendor support
- 一個配置了“_databaseId”變量的 databaseIdProvider 對于動態(tài)代碼來說是可用的格侯,這樣就可以根據(jù)不同的數(shù)據(jù)庫廠商構(gòu)建特定的語句鼻听。比如下面的例子:
<insert id="insert">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
<if test="_databaseId == 'oracle'">
select seq_users.nextval from dual
</if>
<if test="_databaseId == 'db2'">
select nextval for seq_users from sysibm.sysdummy1"
</if>
</selectKey>
insert into users values (#{id}, #{name})
</insert>
5.7 動態(tài) SQL 中可插拔的腳本語言
MyBatis 從 3.2 開始支持可插拔的腳本語言财著,因此你可以在插入一種語言的驅(qū)動(language driver)之后來寫基于這種語言的動態(tài) SQL 查詢。
可以通過實現(xiàn)下面接口的方式來插入一種語言:
public interface LanguageDriver {
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
- 一旦有了自定義的語言驅(qū)動撑碴,你就可以在 mybatis-config.xml 文件中將它設(shè)置為默認語言:
<typeAliases>
<typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
<setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>
- 除了設(shè)置默認語言撑教,你也可以針對特殊的語句指定特定語言,這可以通過如下的 lang 屬性來完成:
<select id="selectBlog" lang="myLanguage">
SELECT * FROM BLOG
</select>
- 或者在你正在使用的映射中加上注解 @Lang 來完成:
public interface Mapper {
@Lang(MyLanguageDriver.class)
@Select("SELECT * FROM BLOG")
List<Blog> selectBlog();
}
注意 可以將 Apache Velocity 作為動態(tài)語言來使用醉拓,更多細節(jié)請參考 MyBatis-Velocity 項目伟姐。
- 你前面看到的所有 xml 標(biāo)簽都是默認 MyBatis 語言提供的,它是由別名為 xml 語言驅(qū)動器 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver 驅(qū)動的亿卤。
6. Java API
6.1 應(yīng)用目錄結(jié)構(gòu)
6.2 SqlSessions
- 既然你已經(jīng)知道如何配置 MyBatis 和創(chuàng)建映射文件,你就已經(jīng)準(zhǔn)備好來提升技能了愤兵。 MyBatis 的 Java API 就是你收獲你所做的努力的地方。正如你即將看到的,和 JDBC 相比, MyBatis 很大程度簡化了你的代碼而且保持簡潔,很容易理解和維護排吴。MyBatis 3 已經(jīng)引入 了很多重要的改進來使得 SQL 映射更加優(yōu)秀秆乳。
6.1 應(yīng)用目錄結(jié)構(gòu)
在我們深入 Java API 之前,理解關(guān)于目錄結(jié)構(gòu)的最佳實踐是很重要的。MyBatis 非常靈 活, 你可以用你自己的文件來做幾乎所有的事情钻哩。 但是對于任一框架, 都有一些最佳的方式屹堰。
讓我們看一下典型應(yīng)用的目錄結(jié)構(gòu):
/my_application
/bin
/devlib
/lib <-- MyBatis *.jar文件在這里。
/src
/org/myapp/
/action
/data <-- MyBatis配置文件在這里, 包括映射器類, XML配置, XML映射文件街氢。
/mybatis-config.xml
/BlogMapper.java
/BlogMapper.xml
/model
/service
/view
/properties <-- 在你XML中配置的屬性 文件在這里扯键。
/test
/org/myapp/
/action
/data
/model
/service
/view
/properties
/web
/WEB-INF
/web.xml
Remember, these are preferences, not requirements, but others will thank you for using a common directory structure.
這部分內(nèi)容剩余的示例將假設(shè)你使用了這種目錄結(jié)構(gòu)。
6.2 SqlSessions
- 使用 MyBatis 的主要 Java 接口就是 SqlSession珊肃。盡管你可以使用這個接口執(zhí)行命令,獲 取映射器和管理事務(wù)荣刑。我們會討論 SqlSession 本身更多,但是首先我們還是要了解如何獲取 一個 SqlSession 實例。SqlSessions 是由 SqlSessionFactory 實例創(chuàng)建的伦乔。SqlSessionFactory 對 象 包 含 創(chuàng) 建 SqlSession 實 例 的 所 有 方 法 嘶摊。 而 SqlSessionFactory 本 身 是 由 SqlSessionFactoryBuilder 創(chuàng)建的,它可以從 XML 配置,注解或手動配置 Java 來創(chuàng)建 SqlSessionFactory。
NOTE When using MyBatis with a dependency injection framework like Spring or Guice, SqlSessions are created and injected by the DI framework so you don't need to use the SqlSessionFactoryBuilder or SqlSessionFactory and can go directly to the SqlSession section. Please refer to the MyBatis-Spring or MyBatis-Guice manuals for further info.
6.2.1 SqlSessionFactoryBuilder
- SqlSessionFactoryBuilder 有五個 build()方法,每一種都允許你從不同的資源中創(chuàng)建一個 SqlSession 實例评矩。
SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)
- 第一種方法是最常用的,它使用了一個參照了 XML 文檔或上面討論過的更特定的 mybatis-config.xml 文件的 Reader 實例。 可選的參數(shù)是 environment 和 properties阱飘。 Environment 決定加載哪種環(huán)境,包括數(shù)據(jù)源和事務(wù)管理器斥杜。比如:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
...
<dataSource type="POOLED">
...
</environment>
<environment id="production">
<transactionManager type="MANAGED">
...
<dataSource type="JNDI">
...
</environment>
</environments>
如果你調(diào)用了 一個使用 environment 參數(shù) 方 式的 build 方法, 那么 MyBatis 將會使用 configuration 對象來配置這個 environment。 當(dāng)然, 如果你指定了一個不合法的 environment, 你會得到錯誤提示沥匈。 如果你調(diào)用了其中之一沒有 environment 參數(shù)的 build 方法, 那么就使用 默認的 environment(在上面的示例中就會指定為 default=”development”)蔗喂。
如果你調(diào)用了使用 properties 實例的方法,那么 MyBatis 就會加載那些 properties(屬性 配置文件) ,并你在你配置中可使用它們。那些屬性可以用${propName}語法形式多次用在 配置文件中高帖。
回想一下,屬性可以從 mybatis-config.xml 中被引用,或者直接指定它缰儿。因此理解優(yōu)先 級是很重要的。我們在文檔前面已經(jīng)提及它了,但是這里要再次重申:
-
如果一個屬性存在于這些位置,那么 MyBatis 將會按找下面的順序來加載它們:
- 在 properties 元素體中指定的屬性首先被讀取,
- 從 properties 元素的類路徑 resource 或 url 指定的屬性第二個被讀取, 可以覆蓋已經(jīng) 指定的重復(fù)屬性,
- 作為方法參 數(shù)傳遞 的屬性最 后被讀 取,可以 覆蓋已 經(jīng)從 properties 元 素體和 resource/url 屬性中加載的任意重復(fù)屬性散址。
因此,最高優(yōu)先級的屬性是通過方法參數(shù)傳遞的,之后是 resource/url 屬性指定的,最 后是在 properties 元素體中指定的屬性乖阵。
總結(jié)一下,前四個方法很大程度上是相同的,但是由于可以覆蓋,就允許你可選地指定 environment 和/或 properties宣赔。 這里給出一個從 mybatis-config.xml 文件創(chuàng)建 SqlSessionFactory 的示例:
String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
- 注意這里我們使用了 Resources 工具類,這個類在 org.mybatis.io 包中。Resources 類正 如其名,會幫助你從類路徑下,文件系統(tǒng)或一個 web URL 加載資源文件瞪浸∪褰看一下這個類的 源代碼或者通過你的 IDE 來查看,就會看到一整套有用的方法。這里給出一個簡表:
URL getResourceURL(String resource)
URL getResourceURL(ClassLoader loader, String resource)
InputStream getResourceAsStream(String resource)
InputStream getResourceAsStream(ClassLoader loader, String resource)
Properties getResourceAsProperties(String resource)
Properties getResourceAsProperties(ClassLoader loader, String resource)
Reader getResourceAsReader(String resource)
Reader getResourceAsReader(ClassLoader loader, String resource)
File getResourceAsFile(String resource)
File getResourceAsFile(ClassLoader loader, String resource)
InputStream getUrlAsStream(String urlString)
Reader getUrlAsReader(String urlString)
Properties getUrlAsProperties(String urlString)
Class classForName(String className)
- 最后一個 build 方法使用了一個 Configuration 實例对蒲。configuration 類包含你可能需要了 解 SqlSessionFactory 實例的所有內(nèi)容钩蚊。Configuration 類對于配置的自查很有用,包含查找和 操作 SQL 映射(不推薦使用,因為應(yīng)用正接收請求) 。configuration 類有所有配置的開關(guān), 這些你已經(jīng)了解了,只在 Java API 中露出來蹈矮。這里有一個簡單的示例,如何手動配置 configuration 實例,然后將它傳遞給 build()方法來創(chuàng)建 SqlSessionFactory砰逻。
DataSource dataSource = BaseDataTest.createBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.setLazyLoadingEnabled(true);
configuration.setEnhancementEnabled(true);
configuration.getTypeAliasRegistry().registerAlias(Blog.class);
configuration.getTypeAliasRegistry().registerAlias(Post.class);
configuration.getTypeAliasRegistry().registerAlias(Author.class);
configuration.addMapper(BoundBlogMapper.class);
configuration.addMapper(BoundAuthorMapper.class);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(configuration);
- 現(xiàn)在你有一個 SqlSessionFactory,可以用來創(chuàng)建 SqlSession 實例。
6.2.2 SqlSessionFactory
-
SqlSessionFactory 有六個方法可以用來創(chuàng)建 SqlSession 實例泛鸟。通常來說,如何決定是你 選擇下面這些方法時:
- Transaction (事務(wù)): 你想為 session 使用事務(wù)或者使用自動提交(通常意味著很多 數(shù)據(jù)庫和/或 JDBC 驅(qū)動沒有事務(wù))?
- Connection (連接): 你想 MyBatis 獲得來自配置的數(shù)據(jù)源的連接還是提供你自己
- Execution (執(zhí)行): 你想 MyBatis 復(fù)用預(yù)處理語句和/或批量更新語句(包括插入和 刪除)?
重載的 openSession()方法簽名設(shè)置允許你選擇這些可選中的任何一個組合蝠咆。
SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();
-
默認的 openSession()方法沒有參數(shù),它會創(chuàng)建有如下特性的 SqlSession:
- 會開啟一個事務(wù)(也就是不自動提交)
- 連接對象會從由活動環(huán)境配置的數(shù)據(jù)源實例中得到。
- 事務(wù)隔離級別將會使用驅(qū)動或數(shù)據(jù)源的默認設(shè)置谈况。
- 預(yù)處理語句不會被復(fù)用,也不會批量處理更新勺美。
這些方法大都可以自我解釋的。 開啟自動提交, “true” 傳遞 給可選的 autoCommit 參數(shù)碑韵。 提供自定義的連接,傳遞一個 Connection 實例給 connection 參數(shù)赡茸。注意沒有覆蓋同時設(shè)置 Connection 和 autoCommit 兩者的方法,因為 MyBatis 會使用當(dāng)前 connection 對象提供的設(shè) 置。 MyBatis 為事務(wù)隔離級別調(diào)用使用一個 Java 枚舉包裝器, 稱為 TransactionIsolationLevel, 否則它們按預(yù)期的方式來工作,并有 JDBC 支持的 5 級 ( NONE,READ_UNCOMMITTED,READ_COMMITTED,REPEA TABLE_READ,SERIALIZA BLE)
-
還有一個可能對你來說是新見到的參數(shù),就是 ExecutorType祝闻。這個枚舉類型定義了 3 個 值:
- ExecutorType.SIMPLE: 這個執(zhí)行器類型不做特殊的事情占卧。它為每個語句的執(zhí)行創(chuàng)建一個新的預(yù)處理語句。
- ExecutorType.REUSE: 這個執(zhí)行器類型會復(fù)用預(yù)處理語句联喘。
- ExecutorType.BATCH: 這個執(zhí)行器會批量執(zhí)行所有更新語句,如果 SELECT 在它們中間執(zhí)行還會標(biāo)定它們是 必須的,來保證一個簡單并易于理解的行為华蜒。
注意 在 SqlSessionFactory 中還有一個方法我們沒有提及,就是 getConfiguration()。這 個方法會返回一個 Configuration 實例,在運行時你可以使用它來自檢 MyBatis 的配置豁遭。
注意 如果你已經(jīng)使用之前版本 MyBatis,你要回憶那些 session,transaction 和 batch 都是分離的“认玻現(xiàn)在和以往不同了,這些都包含在 session 的范圍內(nèi)了。你需要處理分開處理 事務(wù)或批量操作來得到它們的效果蓖谢。
6.2.3 SqlSession
如上面所提到的,SqlSession 實例在 MyBatis 中是非常強大的一個類捂蕴。在這里你會發(fā)現(xiàn) 所有執(zhí)行語句的方法,提交或回滾事務(wù),還有獲取映射器實例。
在 SqlSession 類中有超過 20 個方法,所以將它們分開成易于理解的組合闪幽。
6.2.3.1 語句執(zhí)行方法
- 這些方法被用來執(zhí)行定義在 SQL 映射的 XML 文件中的 SELECT,INSERT,UPDA E T 和 DELETE 語句啥辨。它們都會自行解釋,每一句都使用語句的 ID 屬性和參數(shù)對象,參數(shù)可以 是原生類型(自動裝箱或包裝類) ,JavaBean,POJO 或 Map。
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
selectOne 和 selectList 的不同僅僅是 selectOne 必須返回一個對象盯腌。 如果多余一個, 或者 沒有返回 (或返回了 null) 那么就會拋出異常溉知。 , 如果你不知道需要多少對象, 使用 selectList。
如果你想檢查一個對象是否存在,那么最好返回統(tǒng)計數(shù)(0 或 1) 。因為并不是所有語句都需 要參數(shù),這些方法都是有不同重載版本的,它們可以不需要參數(shù)對象级乍。
<T> T selectOne(String statement)
<E> List<E> selectList(String statement)
<K,V> Map<K,V> selectMap(String statement, String mapKey)
int insert(String statement)
int update(String statement)
int delete(String statement)
- 最后,還有查詢方法的三個高級版本,它們允許你限制返回行數(shù)的范圍,或者提供自定 義結(jié)果控制邏輯,這通常用于大量的數(shù)據(jù)集合舌劳。
<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)
- RowBounds 參數(shù)會告訴 MyBatis 略過指定數(shù)量的記錄,還有限制返回結(jié)果的數(shù)量。 RowBounds 類有一個構(gòu)造方法來接收 offset 和 limit,否則是不可改變的卡者。
int offset = 100;
int limit = 25;
RowBounds rowBounds = new RowBounds(offset, limit);
不同的驅(qū)動會實現(xiàn)這方面的不同級別的效率蒿囤。對于最佳的表現(xiàn),使用結(jié)果集類型的 SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE(或句話說:不是 FORWARD_ONLY)。
ResultHandler 參數(shù)允許你按你喜歡的方式處理每一行崇决。你可以將它添加到 List 中,創(chuàng) 建 Map, 或拋出每個結(jié)果而不是只保留總計材诽。 Set 你可以使用 ResultHandler 做很多漂亮的事, 那就是 MyBatis 內(nèi)部創(chuàng)建結(jié)果集列表。
它的接口很簡單恒傻。
package org.apache.ibatis.session;
public interface ResultHandler<T> {
void handleResult(ResultContext<? extends T> context);
}
- ResultContext 參數(shù)給你訪問結(jié)果對象本身的方法, 大量結(jié)果對象被創(chuàng)建, 你可以使用布 爾返回值的 stop()方法來停止 MyBatis 加載更多的結(jié)果脸侥。
6.2.3.2 Batch update statement Flush Method
- There is method for flushing(executing) batch update statements that stored in a JDBC driver class at any timing. This method can be used when you use the ExecutorType.BATCH as ExecutorType.
List<BatchResult> flushStatements()
6.2.3.3 事務(wù)控制方法
- 控制事務(wù)范圍有四個方法。 當(dāng)然, 如果你已經(jīng)選擇了自動提交或你正在使用外部事務(wù)管 理器,這就沒有任何效果了盈厘。然而,如果你正在使用 JDBC 事務(wù)管理員,由 Connection 實 例來控制,那么這四個方法就會派上用場:
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)
- 默認情況下 MyBatis 不會自動提交事務(wù), 除非它偵測到有插入, 更新或刪除操作改變了 數(shù)據(jù)庫睁枕。如果你已經(jīng)做出了一些改變而沒有使用這些方法,那么你可以傳遞 true 到 commit 和 rollback 方法來保證它會被提交(注意,你不能在自動提交模式下強制 session,或者使用 了外部事務(wù)管理器時) 。很多時候你不用調(diào)用 rollback(),因為如果你沒有調(diào)用 commit 時 MyBatis 會替你完成沸手。然而,如果你需要更多對多提交和回滾都可能的 session 的細粒度控 制,你可以使用回滾選擇來使它成為可能外遇。
NOTE MyBatis-Spring and MyBatis-Guice provide declarative transaction handling. So if you are using MyBatis with Spring or Guice please refer to their specific manuals.
6.2.3.4 清理 Session 級的緩存
void clearCache()
- SqlSession 實例有一個本地緩存在執(zhí)行 update,commit,rollback 和 close 時被清理。要 明確地關(guān)閉它(獲取打算做更多的工作) ,你可以調(diào)用 clearCache()契吉。
6.2.3.5 確保 SqlSession 被關(guān)閉
void close()
- 你必須保證的最重要的事情是你要關(guān)閉所打開的任何 session跳仿。保證做到這點的最佳方 式是下面的工作模式:
SqlSession session = sqlSessionFactory.openSession();
try {
// following 3 lines pseudocod for "doing some work"
session.insert(...);
session.update(...);
session.delete(...);
session.commit();
} finally {
session.close();
}
- 還有,如果你正在使用jdk 1.7以上的版本還有MyBatis 3.2以上的版本捐晶,你可以使用try-with-resources語句:
try (SqlSession session = sqlSessionFactory.openSession()) {
// following 3 lines pseudocode for "doing some work"
session.insert(...);
session.update(...);
session.delete(...);
session.commit();
}
注意 就像 SqlSessionFactory,你可以通過調(diào)用 getConfiguration()方法獲得 SqlSession 使用的 Configuration 實例
Configuration getConfiguration()
6.2.3.6 使用映射器
<T> T getMapper(Class<T> type)
上述的各個 insert,update,delete 和 select 方法都很強大,但也有些繁瑣,沒有類型安 全,對于你的 IDE 也沒有幫助,還有可能的單元測試菲语。在上面的入門章節(jié)中我們已經(jīng)看到 了一個使用映射器的示例。
因此, 一個更通用的方式來執(zhí)行映射語句是使用映射器類惑灵。 一個映射器類就是一個簡單 的接口,其中的方法定義匹配于 SqlSession 方法山上。下面的示例展示了一些方法簽名和它們是 如何映射到 SqlSession 的。
public interface AuthorMapper {
// (Author) selectOne("selectAuthor",5);
Author selectAuthor(int id);
// (List<Author>) selectList(“selectAuthors”)
List<Author> selectAuthors();
// (Map<Integer,Author>) selectMap("selectAuthors", "id")
@MapKey("id")
Map<Integer, Author> selectAuthors();
// insert("insertAuthor", author)
int insertAuthor(Author author);
// updateAuthor("updateAuthor", author)
int updateAuthor(Author author);
// delete("deleteAuthor",5)
int deleteAuthor(int id);
}
總之, 每個映射器方法簽名應(yīng)該匹配相關(guān)聯(lián)的 SqlSession 方法, 而沒有字符串參數(shù) ID英支。 相反,方法名必須匹配映射語句的 ID佩憾。
此外,返回類型必須匹配期望的結(jié)果類型。所有常用的類型都是支持的,包括:原生類 型,Map,POJO 和 JavaBean干花。
映射器接口不需要去實現(xiàn)任何接口或擴展任何類妄帘。 只要方法前面可以被用來唯一標(biāo)識對 應(yīng)的映射語句就可以了。
映射器接口可以擴展其他接口把敢。當(dāng)使用 XML 來構(gòu)建映射器接口時要保證在合適的命名 空間中有語句。 而且, 唯一的限制就是你不能在兩個繼承關(guān)系的接口中有相同的方法簽名 (這 也是不好的想法)谅辣。
你可以傳遞多個參數(shù)給一個映射器方法修赞。 如果你這樣做了, 默認情況下它們將會以它們 在參數(shù)列表中的位置來命名,比如:#{param1},#{param2}等。如果你想改變參數(shù)的名稱(只在多參數(shù) 情況下) ,那么你可以在參數(shù)上使用@Param(“paramName”)注解。
你也可以給方法傳遞一個 RowBounds 實例來限制查詢結(jié)果柏副。
6.2.3.7 映射器注解
- 因為最初設(shè)計時,MyBatis 是一個 XML 驅(qū)動的框架。配置信息是基于 XML 的,而且 映射語句也是定義在 XML 中的。而到了 MyBatis 3,有新的可用的選擇了淘捡。MyBatis 3 構(gòu)建 在基于全面而且強大的 Java 配置 API 之上冒萄。這個配置 API 是基于 XML 的 MyBatis 配置的 基礎(chǔ),也是新的基于注解配置的基礎(chǔ)。注解提供了一種簡單的方式來實現(xiàn)簡單映射語句,而 不會引入大量的開銷荔泳。
注意 不幸的是,Java 注解限制了它們的表現(xiàn)和靈活蕉饼。盡管很多時間都花調(diào)查,設(shè)計和 實驗上,最強大的 MyBatis 映射不能用注解來構(gòu)建,那并不可笑。C#屬性(做示例)就沒 有這些限制,因此 MyBatis.NET 將會比 XML 有更豐富的選擇玛歌。也就是說,基于 Java 注解 的配置離不開它的特性昧港。
注解有下面這些:
@CacheNamespace
- 類
- <cache>
- 為給定的命名空間 (比如類) 配置緩存。 屬性:implemetation,eviction, flushInterval,size 和 readWrite支子。
@CacheNamespaceRef
- 類
- <cacheRef>
- 參照另外一個命名空間的緩存來使用创肥。 屬性:value,應(yīng)該是一個名空間的字 符串值(也就是類的完全限定名) 。
@ConstructorArgs
- Method
- <constructor>
- 收集一組結(jié)果傳遞給一個劫奪對象的 構(gòu)造方法值朋。屬性:value,是形式參數(shù) 的數(shù)組叹侄。
@Arg
- 方法
- 對應(yīng)的XML
- <arg>
- <idArg>
- 單 獨 的 構(gòu) 造 方 法 參 數(shù) , 是 ConstructorArgs 集合的一部分。屬性: id,column,javaType,typeHandler昨登。 id 屬性是布爾值, 來標(biāo)識用于比較的屬 性,和<idArg>XML 元素相似趾代。
@TypeDiscriminator
- 方法
- <discriminator>
- 一組實例值被用來決定結(jié)果映射的表 現(xiàn)。 屬性: column, javaType, jdbcType, typeHandler,cases篙骡。cases 屬性就是實 例的數(shù)組稽坤。
@Case
- 方法
- <case>
- 單獨實例的值和它對應(yīng)的映射。屬性: value,type,results糯俗。Results 屬性是結(jié) 果數(shù)組,因此這個注解和實際的 ResultMap 很相似,由下面的 Results 注解指定尿褪。
@Results
- 方法
- <resultMap>
- 結(jié)果映射的列表, 包含了一個特別結(jié)果 列如何被映射到屬性或字段的詳情。 屬 性:value, id得湘。value 屬性是 Result 注解的數(shù)組杖玲。 The id attribute is the name of the result mapping.
@Result
- 方法
- 對應(yīng)的XML
- <result>
- <id>
- 在列和屬性或字段之間的單獨結(jié)果映 射。屬 性:id,column, property, javaType ,jdbcType ,type Handler, one,many淘正。id 屬性是一個布爾值,表 示了應(yīng)該被用于比較(和在 XML 映射 中的<id>相似)的屬性摆马。one 屬性是單 獨 的 聯(lián) 系, 和 <association> 相 似 , 而 many 屬 性 是 對 集 合 而 言 的 , 和 <collection>相似。 它們這樣命名是為了 避免名稱沖突鸿吆。
@One
- 方法
- <association>
- 復(fù)雜類型的單獨屬性值映射囤采。屬性: select,已映射語句(也就是映射器方 法)的完全限定名,它可以加載合適類 型的實例。注意:聯(lián)合映射在注解 API 中是不支持的惩淳。這是因為 Java 注解的 限制,不允許循環(huán)引用蕉毯。 fetchType, which supersedes the global configuration parameter lazyLoadingEnabled for this mapping.
@Many
- 方法
- <collection>
- A mapping to a collection property of a complex type. Attributes: select, which is the fully qualified name of a mapped statement (i.e. mapper method) that can load a collection of instances of the appropriate types, fetchType, which supersedes the global configuration parameter lazyLoadingEnabled for this mapping. NOTE You will notice that join mapping is not supported via the Annotations API. This is due to the limitation in Java Annotations that does not allow for circular references.
@MapKey
- 方法
- (沒有對應(yīng)的XML)
- 復(fù) 雜 類 型 的 集合 屬 性 映射 乓搬。 屬 性 : select,是映射語句(也就是映射器方 法)的完全限定名,它可以加載合適類 型的一組實例。注意:聯(lián)合映射在 Java 注解中是不支持的代虾。這是因為 Java 注 解的限制,不允許循環(huán)引用进肯。
@Options
- 方法
- 映射語句的屬性
- 這個注解提供訪問交換和配置選項的 寬廣范圍, 它們通常在映射語句上作為 屬性出現(xiàn)。 而不是將每條語句注解變復(fù) 雜,Options 注解提供連貫清晰的方式 來訪問它們棉磨。屬性:useCache=true , flushCache=FlushCachePolicy.DEFAULT , resultSetType=FORWARD_ONLY , statementType=PREPARED , fetchSize=-1 , , timeout=-1 useGeneratedKeys=false , keyProperty=”id” , keyColumn=”” , resultSets=””江掩。 理解 Java 注解是很 重要的,因為沒有辦法來指定“null” 作為值。因此,一旦你使用了 Options 注解,語句就受所有默認值的支配乘瓤。要 注意什么樣的默認值來避免不期望的 行為环形。
@Insert/@Update/@Delete/@Select
- 方法
- 對應(yīng)的XML
- <insert>
- <update>
- <delete>
- <select>
- 這些注解中的每一個代表了執(zhí)行的真 實 SQL。 它們每一個都使用字符串?dāng)?shù)組 (或單獨的字符串)馅扣。如果傳遞的是字 符串?dāng)?shù)組, 它們由每個分隔它們的單獨 空間串聯(lián)起來斟赚。這就當(dāng)用 Java 代碼構(gòu) 建 SQL 時避免了“丟失空間”的問題。 然而,如果你喜歡,也歡迎你串聯(lián)單獨 的字符串差油。屬性:value,這是字符串 數(shù)組用來組成單獨的 SQL 語句拗军。
@InsertProvider/@UpdateProvider/@DeleteProvider/@SelectProvider
- 方法
- 對應(yīng)的XML
- <insert>
- <update>
- <delete>
- <select>
- 這些可選的 SQL 注解允許你指定一個 類名和一個方法在執(zhí)行時來返回運行 允許創(chuàng)建動態(tài) 的 SQL。 基于執(zhí)行的映射語句, MyBatis 會實例化這個類,然后執(zhí)行由 provider 指定的方法. 該方法可以有選擇地接受參數(shù)對象.(In MyBatis 3.4 or later, it's allow multiple parameters) 屬性: type,method蓄喇。type 屬性是類发侵。method 屬性是方法名。 注意: 這節(jié)之后是對 類的 討論,它可以幫助你以干凈,容于閱讀 的方式來構(gòu)建動態(tài) SQL妆偏。
@Param
- Parameter
- N/A
- 如果你的映射器的方法需要多個參數(shù), 這個注解可以被應(yīng)用于映射器的方法 參數(shù)來給每個參數(shù)一個名字刃鳄。否則,多 參數(shù)將會以它們的順序位置來被命名 (不包括任何 RowBounds 參數(shù)) 比如。 #{param1} , #{param2} 等 , 這 是 默 認 的 钱骂。 使 用 @Param(“person”),參數(shù)應(yīng)該被命名為 #{person}叔锐。
@SelectKey
- Method
- <selectKey>
- This annotation duplicates the <selectKey> functionality for methods annotated with @Insert, @InsertProvider, @Update or @UpdateProvider. It is ignored for other methods. If you specify a @SelectKey annotation, then MyBatis will ignore any generated key properties set via the @Options annotation, or configuration properties. Attributes: statement an array of strings which is the SQL statement to execute, keyProperty which is the property of the parameter object that will be updated with the new value, before which must be either true or false to denote if the SQL statement should be executed before or after the insert, resultType which is the Java type of the keyProperty, and statementType=PREPARED.
@ResultMap
- Method
- N/A
- This annotation is used to provide the id of a <resultMap> element in an XML mapper to a @Select or
@SelectProvider annotation. This allows annotated selects to reuse resultmaps that are defined in XML. This annotation will override any @Results or @ConstructorArgs annotation if both are specified on an annotated select.
@ResultType
- Method
- N/A
@Flush
- Method
- N/A
- If this annotation is used, it can be called the SqlSession#flushStatements() via method defined at a Mapper interface.(MyBatis 3.3 or above)
6.2.3.8 映射申明樣例
- 這個例子展示了如何使用 @SelectKey 注解來在插入前讀取數(shù)據(jù)庫序列的值:
@Insert("insert into table3 (id, name) values(#{nameId}, #{name})")
@SelectKey(statement="call next value for TestSequence", keyProperty="nameId", before=true, resultType=int.class)
int insertTable3(Name name);
- 這個例子展示了如何使用 @SelectKey 注解來在插入后讀取數(shù)據(jù)庫識別列的值:
@Insert("insert into table2 (name) values(#{name})")
@SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class)
int insertTable2(Name name);
- This example shows using the @Flush annotation to call the SqlSession#flushStatements():
@Flush
List<BatchResult> flush();
- These examples show how to name a ResultMap by specifying id attribute of @Results annotation.
@Results(id = "userResult", value = {
@Result(property = "id", column = "uid", id = true),
@Result(property = "firstName", column = "first_name"),
@Result(property = "lastName", column = "last_name")
})
@Select("select * from users where id = #{id}")
User getUserById(Integer id);
@Results(id = "companyResults")
@ConstructorArgs({
@Arg(property = "id", column = "cid", id = true),
@Arg(property = "name", column = "name")
})
@Select("select * from company where id = #{id}")
Company getCompanyById(Integer id);
- This example shows solo parameter using the Sql Provider annotation:
@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(String name);
class UserSqlBuilder {
public String buildGetUsersByName(final String name) {
return new SQL(){{
SELECT("*");
FROM("users");
if (name != null) {
WHERE("name like #{value} || '%'");
}
ORDER_BY("id");
}}.toString();
}
}
- This example shows multiple parameters using the Sql Provider annotation:
@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(
@Param("name") String name, @Param("orderByColumn") String orderByColumn);
class UserSqlBuilder {
// If not use @Param, you should be define same arguments with mapper method
public String buildGetUsersByName(
final String name, final String orderByColumn) {
return new SQL(){{
SELECT("*");
FROM("users");
WHERE("name like #{name} || '%'");
ORDER_BY(orderByColumn);
}}.toString();
}
// If use @Param, you can define only arguments to be used
public String buildGetUsersByName(@Param("orderByColumn") final String orderByColumn) {
return new SQL(){{
SELECT("*");
FROM("users");
WHERE("name like #{name} || '%'");
ORDER_BY(orderByColumn);
}}.toString();
}
}
7. SQL語句構(gòu)建器類
7.1 問題
- Java程序員面對的最痛苦的事情之一就是在Java代碼中嵌入SQL語句。這么來做通常是由于SQL語句需要動態(tài)來生成-否則可以將它們放到外部文件或者存儲過程中见秽。正如你已經(jīng)看到的那樣愉烙,MyBatis在它的XML映射特性中有一個強大的動態(tài)SQL生成方案。但有時在Java代碼內(nèi)部創(chuàng)建SQL語句也是必要的解取。此時步责,MyBatis有另外一個特性可以幫到你,在減少典型的加號,引號,新行,格式化問題和嵌入條件來處理多余的逗號或 AND 連接詞之前禀苦。事實上蔓肯,在Java代碼中來動態(tài)生成SQL代碼就是一場噩夢。例如:
String sql = "SELECT P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME, "
"P.LAST_NAME,P.CREATED_ON, P.UPDATED_ON " +
"FROM PERSON P, ACCOUNT A " +
"INNER JOIN DEPARTMENT D on D.ID = P.DEPARTMENT_ID " +
"INNER JOIN COMPANY C on D.COMPANY_ID = C.ID " +
"WHERE (P.ID = A.ID AND P.FIRST_NAME like ?) " +
"OR (P.LAST_NAME like ?) " +
"GROUP BY P.ID " +
"HAVING (P.LAST_NAME like ?) " +
"OR (P.FIRST_NAME like ?) " +
"ORDER BY P.ID, P.FULL_NAME";
7.2 The Solution
- MyBatis 3提供了方便的工具類來幫助解決該問題振乏。使用SQL類蔗包,簡單地創(chuàng)建一個實例來調(diào)用方法生成SQL語句。上面示例中的問題就像重寫SQL類那樣:
private String selectPersonSql() {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
FROM("PERSON P");
FROM("ACCOUNT A");
INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
WHERE("P.ID = A.ID");
WHERE("P.FIRST_NAME like ?");
OR();
WHERE("P.LAST_NAME like ?");
GROUP_BY("P.ID");
HAVING("P.LAST_NAME like ?");
OR();
HAVING("P.FIRST_NAME like ?");
ORDER_BY("P.ID");
ORDER_BY("P.FULL_NAME");
}}.toString();
}
- 該例中有什么特殊之處慧邮?當(dāng)你仔細看時调限,那不用擔(dān)心偶然間重復(fù)出現(xiàn)的"AND"關(guān)鍵字邻储,或者在"WHERE"和"AND"之間的選擇,抑或什么都不選旧噪。該SQL類非常注意"WHERE"應(yīng)該出現(xiàn)在何處,哪里又應(yīng)該使用"AND"脓匿,還有所有的字符串鏈接淘钟。
7.3 SQL類
- 這里給出一些示例:
// Anonymous inner class
public String deletePersonSql() {
return new SQL() {{
DELETE_FROM("PERSON");
WHERE("ID = #{id}");
}}.toString();
}
// Builder / Fluent style
public String insertPersonSql() {
String sql = new SQL()
.INSERT_INTO("PERSON")
.VALUES("ID, FIRST_NAME", "#{id}, #{firstName}")
.VALUES("LAST_NAME", "#{lastName}")
.toString();
return sql;
}
// With conditionals (note the final parameters, required for the anonymous inner class to access them)
public String selectPersonLike(final String id, final String firstName, final String lastName) {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
FROM("PERSON P");
if (id != null) {
WHERE("P.ID like #{id}");
}
if (firstName != null) {
WHERE("P.FIRST_NAME like #{firstName}");
}
if (lastName != null) {
WHERE("P.LAST_NAME like #{lastName}");
}
ORDER_BY("P.LAST_NAME");
}}.toString();
}
public String deletePersonSql() {
return new SQL() {{
DELETE_FROM("PERSON");
WHERE("ID = #{id}");
}}.toString();
}
public String insertPersonSql() {
return new SQL() {{
INSERT_INTO("PERSON");
VALUES("ID, FIRST_NAME", "#{id}, #{firstName}");
VALUES("LAST_NAME", "#{lastName}");
}}.toString();
}
public String updatePersonSql() {
return new SQL() {{
UPDATE("PERSON");
SET("FIRST_NAME = #{firstName}");
WHERE("ID = #{id}");
}}.toString();
}
SELECT(String)
- 開始或插入到 SELECT子句。 可以被多次調(diào)用陪毡,參數(shù)也會添加到 SELECT子句米母。 參數(shù)通常使用逗號分隔的列名和別名列表,但也可以是數(shù)據(jù)庫驅(qū)動程序接受的任意類型毡琉。
SELECT_DISTINCT(String)
- 開始或插入到 SELECT子句铁瞒, 也可以插入 DISTINCT關(guān)鍵字到生成的查詢語句中。 可以被多次調(diào)用桅滋,參數(shù)也會添加到 SELECT子句慧耍。 參數(shù)通常使用逗號分隔的列名和別名列表,但也可以是數(shù)據(jù)庫驅(qū)動程序接受的任意類型丐谋。
FROM(String)
- 開始或插入到 FROM子句芍碧。 可以被多次調(diào)用,參數(shù)也會添加到 FROM子句号俐。 參數(shù)通常是表名或別名泌豆,也可以是數(shù)據(jù)庫驅(qū)動程序接受的任意類型。
JOIN(String) /INNER_JOIN(String) /LEFT_OUTER_JOIN(String) /RIGHT_OUTER_JOIN(String)
- 基于調(diào)用的方法吏饿,添加新的合適類型的 JOIN子句踪危。 參數(shù)可以包含由列命和join on條件組合成標(biāo)準(zhǔn)的join。
WHERE(String)
- 插入新的 WHERE子句條件猪落, 由AND鏈接贞远。可以多次被調(diào)用许布,每次都由AND來鏈接新條件兴革。使用 OR() 來分隔OR。
OR()
- 使用OR來分隔當(dāng)前的 WHERE子句條件蜜唾。 可以被多次調(diào)用杂曲,但在一行中多次調(diào)用或生成不穩(wěn)定的SQL。
AND()
- 使用AND來分隔當(dāng)前的 WHERE子句條件袁余。 可以被多次調(diào)用擎勘,但在一行中多次調(diào)用或生成不穩(wěn)定的SQL。因為 WHERE 和 HAVING 二者都會自動鏈接 AND, 這是非常罕見的方法颖榜,只是為了完整性才被使用棚饵。
GROUP_BY(String)
- 插入新的 GROUP BY子句元素煤裙,由逗號連接。 可以被多次調(diào)用噪漾,每次都由逗號連接新的條件硼砰。
HAVING(String)
- 插入新的 HAVING子句條件。 由AND連接欣硼√夂玻可以被多次調(diào)用,每次都由AND來連接新的條件诈胜。使用 OR() 來分隔OR.
ORDER_BY(String)
- 插入新的 ORDER BY子句元素豹障, 由逗號連接〗剐伲可以多次被調(diào)用血公,每次由逗號連接新的條件。
DELETE_FROM(String)
- 開始一個delete語句并指定需要從哪個表刪除的表名缓熟。通常它后面都會跟著WHERE語句累魔!
INSERT_INTO(String)
- 開始一個insert語句并指定需要插入數(shù)據(jù)的表名。后面都會跟著一個或者多個VALUES()够滑。
SET(String)
- 針對update語句薛夜,插入到"set"列表中
UPDATE(String)
- 開始一個update語句并指定需要更新的表明。后面都會跟著一個或者多個SET()版述,通常也會有一個WHERE()梯澜。
VALUES(String, String)
- 插入到insert語句中。第一個參數(shù)是要插入的列名渴析,第二個參數(shù)則是該列的值晚伙。
7.4 SqlBuilder 和 SelectBuilder (已經(jīng)廢棄)
在3.2版本之前,我們使用了一點不同的做法俭茧,通過實現(xiàn)ThreadLocal變量來掩蓋一些導(dǎo)致Java DSL麻煩的語言限制咆疗。但這種方式已經(jīng)廢棄了,現(xiàn)代的框架都歡迎人們使用構(gòu)建器類型和匿名內(nèi)部類的想法母债。因此午磁,SelectBuilder 和 SqlBuilder 類都被廢棄了。
下面的方法僅僅適用于廢棄的SqlBuilder 和 SelectBuilder 類毡们。
BEGIN()/RESET()
- 這些方法清空SelectBuilder類的ThreadLocal狀態(tài)迅皇,并且準(zhǔn)備一個新的構(gòu)建語句。開始新的語句時衙熔, BEGIN()讀取得最好登颓。 由于一些原因(在某些條件下,也許是邏輯需要一個完全不同的語句)红氯,在執(zhí)行中清理語句 RESET()讀取得最好框咙。
SQL()
- 返回生成的 SQL() 并重置 SelectBuilder 狀態(tài) (好像 BEGIN() 或 RESET() 被調(diào)用了). 因此咕痛,該方法只能被調(diào)用一次!
- SelectBuilder 和 SqlBuilder 類并不神奇喇嘱,但是知道它們?nèi)绾喂ぷ饕彩呛苤匾摹?SelectBuilder 使用 SqlBuilder 使用了靜態(tài)導(dǎo)入和ThreadLocal變量的組合來開啟整潔語法茉贡,可以很容易地和條件交錯。使用它們者铜,靜態(tài)導(dǎo)入類的方法即可块仆,就像這樣(一個或其它,并非兩者):
import static org.apache.ibatis.jdbc.SelectBuilder.*;
import static org.apache.ibatis.jdbc.SqlBuilder.*;
- 這就允許像下面這樣來創(chuàng)建方法:
/* DEPRECATED */
public String selectBlogsSql() {
BEGIN(); // Clears ThreadLocal variable
SELECT("*");
FROM("BLOG");
return SQL();
}
/* DEPRECATED */
private String selectPersonSql() {
BEGIN(); // Clears ThreadLocal variable
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
FROM("PERSON P");
FROM("ACCOUNT A");
INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
WHERE("P.ID = A.ID");
WHERE("P.FIRST_NAME like ?");
OR();
WHERE("P.LAST_NAME like ?");
GROUP_BY("P.ID");
HAVING("P.LAST_NAME like ?");
OR();
HAVING("P.FIRST_NAME like ?");
ORDER_BY("P.ID");
ORDER_BY("P.FULL_NAME");
return SQL();
}