從 XML 中構(gòu)建 SqlSessionFactory
每個(gè)基于 MyBatis 的應(yīng)用都是以一個(gè) SqlSessionFactory 的實(shí)例為中心的翼闹。SqlSessionFactory 的實(shí)例可以通過(guò) SqlSessionFactoryBuilder 獲得。而 SqlSessionFactoryBuilder 則可以從 XML 配置文件或一個(gè)預(yù)先定制的 Configuration 的實(shí)例構(gòu)建出 SqlSessionFactory 的實(shí)例蒋纬。
從 XML 文件中構(gòu)建 SqlSessionFactory 的實(shí)例非常簡(jiǎn)單猎荠,建議使用類(lèi)路徑下的資源文件進(jìn)行配置。但是也可以使用任意的輸入流(InputStream)實(shí)例蜀备,包括字符串形式的文件路徑或者 file:// 的 URL 形式的文件路徑來(lái)配置关摇。MyBatis 包含一個(gè)名叫 Resources 的工具類(lèi),它包含一些實(shí)用方法琼掠,可使從 classpath 或其他位置加載資源文件更加容易。
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
XML 配置文件(configuration XML)中包含了對(duì) MyBatis 系統(tǒng)的核心設(shè)置停撞,包含獲取數(shù)據(jù)庫(kù)連接實(shí)例的數(shù)據(jù)源(DataSource)和決定事務(wù)作用域和控制方式的事務(wù)管理器(TransactionManager)瓷蛙。XML 配置文件的詳細(xì)內(nèi)容后面再探討,這里先給出一個(gè)簡(jiǎn)單的示例:
<?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 文件中進(jìn)行配置艰猬,上面的示例指出的則是最關(guān)鍵的部分。要注意 XML 頭部的聲明埋市,用來(lái)驗(yàn)證 XML 文檔正確性冠桃。environment 元素體中包含了事務(wù)管理和連接池的配置。mappers 元素則是包含一組 mapper 映射器(這些 mapper 的 XML 文件包含了 SQL 代碼和映射定義信息)道宅。
[不使用 XML 構(gòu)建 SqlSessionFactory]
如果你更愿意直接從 Java 程序而不是 XML 文件中創(chuàng)建 configuration食听,或者創(chuàng)建你自己的 configuration 構(gòu)建器,MyBatis 也提供了完整的配置類(lèi)污茵,提供所有和 XML 文件相同功能的配置項(xiàng)樱报。
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 添加了一個(gè)映射器類(lèi)(mapper class)泞当。映射器類(lèi)是 Java 類(lèi)迹蛤,它們包含 SQL 映射語(yǔ)句的注解從而避免了 XML 文件的依賴(lài)。不過(guò),由于 Java 注解的一些限制加之某些 MyBatis 映射的復(fù)雜性盗飒,XML 映射對(duì)于大多數(shù)高級(jí)映射(比如:嵌套 Join 映射)來(lái)說(shuō)仍然是必須的嚷量。有鑒于此,如果存在一個(gè)對(duì)等的 XML 配置文件的話(huà)逆趣,MyBatis 會(huì)自動(dòng)查找并加載它(這種情況下蝶溶, BlogMapper.xml 將會(huì)基于類(lèi)路徑和 BlogMapper.class 的類(lèi)名被加載進(jìn)來(lái))。具體細(xì)節(jié)稍后討論汗贫。
[從 SqlSessionFactory 中獲取 SqlSession]
既然有了 SqlSessionFactory 身坐,顧名思義,我們就可以從中獲得 SqlSession 的實(shí)例了落包。SqlSession 完全包含了面向數(shù)據(jù)庫(kù)執(zhí)行 SQL 命令所需的所有方法部蛇。你可以通過(guò) SqlSession 實(shí)例來(lái)直接執(zhí)行已映射的 SQL 語(yǔ)句。例如:
SqlSession session = sqlSessionFactory.openSession();
try {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
session.close();
}
誠(chéng)然這種方式能夠正常工作咐蝇,并且對(duì)于使用舊版本 MyBatis 的用戶(hù)來(lái)說(shuō)也比較熟悉涯鲁,不過(guò)現(xiàn)在有了一種更直白的方式。使用對(duì)于給定語(yǔ)句能夠合理描述參數(shù)和返回值的接口(比如說(shuō)BlogMapper.class)有序,你現(xiàn)在不但可以執(zhí)行更清晰和類(lèi)型安全的代碼抹腿,而且還不用擔(dān)心易錯(cuò)的字符串字面值以及強(qiáng)制類(lèi)型轉(zhuǎn)換。
例如:
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
} finally {
session.close();
}
現(xiàn)在我們來(lái)探究一下這里到底是怎么執(zhí)行的旭寿。
[探究已映射的 SQL 語(yǔ)句]
現(xiàn)在警绩,或許你很想知道 SqlSession 和 Mapper 到底執(zhí)行了什么操作,而 SQL 語(yǔ)句映射是個(gè)相當(dāng)大的話(huà)題盅称,可能會(huì)占去文檔的大部分篇幅肩祥。不過(guò)為了讓你能夠了解個(gè)大概,這里會(huì)給出幾個(gè)例子缩膝。
在上面提到的兩個(gè)例子中混狠,一個(gè)語(yǔ)句應(yīng)該是通過(guò) XML 定義,而另外一個(gè)則是通過(guò)注解定義疾层。先看 XML 定義這個(gè)将饺,事實(shí)上 MyBatis 提供的全部特性可以利用基于 XML 的映射語(yǔ)言來(lái)實(shí)現(xiàn),這使得 MyBatis 在過(guò)去的數(shù)年間得以流行痛黎。如果你以前用過(guò) MyBatis予弧,這個(gè)概念應(yīng)該會(huì)比較熟悉。不過(guò) XML 映射文件已經(jīng)有了很多的改進(jìn)湖饱,隨著文檔的進(jìn)行會(huì)愈發(fā)清晰桌肴。這里給出一個(gè)基于 XML 映射語(yǔ)句的示例,它應(yīng)該可以滿(mǎn)足上述示例中 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>
對(duì)于這個(gè)簡(jiǎn)單的例子來(lái)說(shuō)似乎有點(diǎn)小題大做了坠七,但實(shí)際上它是非常輕量級(jí)的水醋。在一個(gè) XML 映射文件中,你想定義多少個(gè)映射語(yǔ)句都是可以的彪置,這樣下來(lái)拄踪,XML 頭部和文檔類(lèi)型聲明占去的部分就顯得微不足道了。文件的剩余部分具有很好的自解釋性拳魁。在命名空間“org.mybatis.example.BlogMapper”中定義了一個(gè)名為“selectBlog”的映射語(yǔ)句惶桐,這樣它就允許你使用指定的完全限定名“org.mybatis.example.BlogMapper.selectBlog”來(lái)調(diào)用映射語(yǔ)句,就像上面的例子中做的那樣:
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
你可能注意到這和使用完全限定名調(diào)用 Java 對(duì)象的方法是相似的潘懊,之所以這樣做是有原因的姚糊。這個(gè)命名可以直接映射到在命名空間中同名的 Mapper 類(lèi),并將已映射的 select 語(yǔ)句中的名字授舟、參數(shù)和返回類(lèi)型匹配成方法救恨。這樣你就可以像上面那樣很容易地調(diào)用這個(gè)對(duì)應(yīng) Mapper 接口的方法。不過(guò)讓我們?cè)倏匆槐橄旅娴睦樱?/p>
BlogMapper mapper = session.getMapper(BlogMapper.class);Blog blog = mapper.selectBlog(101);
第二種方法有很多優(yōu)勢(shì)释树,首先它不是基于字符串常量的肠槽,就會(huì)更安全;其次奢啥,如果你的 IDE 有代碼補(bǔ)全功能秸仙,那么你可以在有了已映射 SQL 語(yǔ)句的基礎(chǔ)之上利用這個(gè)功能。
提示命名空間的一點(diǎn)注釋
命名空間(Namespaces)在之前版本的 MyBatis 中是可選的桩盲,這樣容易引起混淆因此毫無(wú)益處〖偶停現(xiàn)在命名空間則是必須的,且意于簡(jiǎn)單地用更長(zhǎng)的完全限定名來(lái)隔離語(yǔ)句赌结。
命名空間使得你所見(jiàn)到的接口綁定成為可能捞蛋,盡管你覺(jué)得這些東西未必用得上,你還是應(yīng)該遵循這里的規(guī)定以防哪天你改變了主意姑曙。出于長(zhǎng)遠(yuǎn)考慮襟交,使用命名空間迈倍,并將它置于合適的 Java 包命名空間之下伤靠,你將擁有一份更加整潔的代碼并提高了 MyBatis 的可用性。
命名解析:為了減少輸入量啼染,MyBatis 對(duì)所有的命名配置元素(包括語(yǔ)句宴合,結(jié)果映射,緩存等)使用了如下的命名解析規(guī)則迹鹅。
完全限定名(比如“com.mypackage.MyMapper.selectAllThings”)將被直接查找并且找到即用卦洽。
短名稱(chēng)(比如“selectAllThings”)如果全局唯一也可以作為一個(gè)單獨(dú)的引用。如果不唯一斜棚,有兩個(gè)或兩個(gè)以上的相同名稱(chēng)(比如“com.foo.selectAllThings ”和“com.bar.selectAllThings”)阀蒂,那么使用時(shí)就會(huì)收到錯(cuò)誤報(bào)告說(shuō)短名稱(chēng)是不唯一的该窗,這種情況下就必須使用完全限定名。
對(duì)于像 BlogMapper 這樣的映射器類(lèi)(Mapper class)來(lái)說(shuō)蚤霞,還有另一招來(lái)處理酗失。它們的映射的語(yǔ)句可以不需要用 XML 來(lái)做,取而代之的是可以使用 Java 注解昧绣。比如规肴,上面的 XML 示例可被替換如下:
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
對(duì)于簡(jiǎn)單語(yǔ)句來(lái)說(shuō),注解使代碼顯得更加簡(jiǎn)潔夜畴,然而 Java 注解對(duì)于稍微復(fù)雜的語(yǔ)句就會(huì)力不從心并且會(huì)顯得更加混亂拖刃。因此,如果你需要做很復(fù)雜的事情贪绘,那么最好使用 XML 來(lái)映射語(yǔ)句兑牡。
選擇何種方式以及映射語(yǔ)句的定義的一致性對(duì)你來(lái)說(shuō)有多重要這些完全取決于你和你的團(tuán)隊(duì)。換句話(huà)說(shuō)兔簇,永遠(yuǎn)不要拘泥于一種方式发绢,你可以很輕松的在基于注解和 XML 的語(yǔ)句映射方式間自由移植和切換。
[作用域(Scope)和生命周期]
理解我們目前已經(jīng)討論過(guò)的不同作用域和生命周期類(lèi)是至關(guān)重要的垄琐,因?yàn)殄e(cuò)誤的使用會(huì)導(dǎo)致非常嚴(yán)重的并發(fā)問(wèn)題边酒。
提示 對(duì)象生命周期和依賴(lài)注入框架
依賴(lài)注入框架可以創(chuàng)建線(xiàn)程安全的、基于事務(wù)的 SqlSession 和映射器(mapper)并將它們直接注入到你的 bean 中狸窘,因此可以直接忽略它們的生命周期墩朦。如果對(duì)如何通過(guò)依賴(lài)注入框架來(lái)使用 MyBatis 感興趣可以研究一下 MyBatis-Spring 或 MyBatis-Guice 兩個(gè)子項(xiàng)目。
[SqlSessionFactoryBuilder]
這個(gè)類(lèi)可以被實(shí)例化翻擒、使用和丟棄氓涣,一旦創(chuàng)建了 SqlSessionFactory,就不再需要它了陋气。因此 SqlSessionFactoryBuilder 實(shí)例的最佳作用域是方法作用域(也就是局部方法變量)劳吠。你可以重用 SqlSessionFactoryBuilder 來(lái)創(chuàng)建多個(gè) SqlSessionFactory 實(shí)例,但是最好還是不要讓其一直存在以保證所有的 XML 解析資源開(kāi)放給更重要的事情巩趁。
[SqlSessionFactory]
SqlSessionFactory 一旦被創(chuàng)建就應(yīng)該在應(yīng)用的運(yùn)行期間一直存在痒玩,沒(méi)有任何理由對(duì)它進(jìn)行清除或重建。使用 SqlSessionFactory 的最佳實(shí)踐是在應(yīng)用運(yùn)行期間不要重復(fù)創(chuàng)建多次议慰,多次重建 SqlSessionFactory 被視為一種代碼“壞味道(bad smell)”蠢古。因此 SqlSessionFactory 的最佳作用域是應(yīng)用作用域。有很多方法可以做到别凹,最簡(jiǎn)單的就是使用單例模式或者靜態(tài)單例模式草讶。
[SqlSession]
每個(gè)線(xiàn)程都應(yīng)該有它自己的 SqlSession 實(shí)例。SqlSession 的實(shí)例不是線(xiàn)程安全的炉菲,因此是不能被共享的堕战,所以它的最佳的作用域是請(qǐng)求或方法作用域坤溃。絕對(duì)不能將 SqlSession 實(shí)例的引用放在一個(gè)類(lèi)的靜態(tài)域,甚至一個(gè)類(lèi)的實(shí)例變量也不行嘱丢。也絕不能將 SqlSession 實(shí)例的引用放在任何類(lèi)型的管理作用域中浇雹,比如 Servlet 架構(gòu)中的 HttpSession。如果你現(xiàn)在正在使用一種 Web 框架屿讽,要考慮 SqlSession 放在一個(gè)和 HTTP 請(qǐng)求對(duì)象相似的作用域中昭灵。換句話(huà)說(shuō),每次收到的 HTTP 請(qǐng)求伐谈,就可以打開(kāi)一個(gè) SqlSession烂完,返回一個(gè)響應(yīng),就關(guān)閉它诵棵。這個(gè)關(guān)閉操作是很重要的抠蚣,你應(yīng)該把這個(gè)關(guān)閉操作放到 finally 塊中以確保每次都能執(zhí)行關(guān)閉。下面的示例就是一個(gè)確保 SqlSession 關(guān)閉的標(biāo)準(zhǔn)模式:
SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
在你的所有的代碼中一致性地使用這種模式來(lái)保證所有數(shù)據(jù)庫(kù)資源都能被正確地關(guān)閉履澳。
[映射器實(shí)例(Mapper Instances)]
映射器是一個(gè)你創(chuàng)建來(lái)綁定你映射的語(yǔ)句的接口嘶窄。映射器接口的實(shí)例是從 SqlSession 中獲得的。因此從技術(shù)層面講距贷,任何映射器實(shí)例的最大作用域是和請(qǐng)求它們的 SqlSession 相同的柄冲。盡管如此,映射器實(shí)例的最佳作用域是方法作用域忠蝗。也就是說(shuō)现横,映射器實(shí)例應(yīng)該在調(diào)用它們的方法中被請(qǐng)求,用過(guò)之后即可廢棄阁最。并不需要顯式地關(guān)閉映射器實(shí)例戒祠,盡管在整個(gè)請(qǐng)求作用域(request scope)保持映射器實(shí)例也不會(huì)有什么問(wèn)題,但是很快你會(huì)發(fā)現(xiàn)速种,像 SqlSession 一樣姜盈,在這個(gè)作用域上管理太多的資源的話(huà)會(huì)難于控制。所以要保持簡(jiǎn)單配阵,最好把映射器放在方法作用域(method scope)內(nèi)馏颂。下面的示例就展示了這個(gè)實(shí)踐:
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class); // do work
} finally {
session.close();
}