MyBatis 流式查詢(xún)

基本概念

流式查詢(xún)指的是查詢(xún)成功后不是返回一個(gè)集合而是返回一個(gè)迭代器,應(yīng)用每次從迭代器取一條查詢(xún)結(jié)果童太。流式查詢(xún)的好處是能夠降低內(nèi)存使用置媳。

如果沒(méi)有流式查詢(xún),我們想要從數(shù)據(jù)庫(kù)取 1000 萬(wàn)條記錄而又沒(méi)有足夠的內(nèi)存時(shí)艘希,就不得不分頁(yè)查詢(xún)硼身,而分頁(yè)查詢(xún)效率取決于表設(shè)計(jì)硅急,如果設(shè)計(jì)的不好,就無(wú)法執(zhí)行高效的分頁(yè)查詢(xún)佳遂。因此流式查詢(xún)是一個(gè)數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)框架必須具備的功能营袜。

流式查詢(xún)的過(guò)程當(dāng)中,數(shù)據(jù)庫(kù)連接是保持打開(kāi)狀態(tài)的丑罪,因此要注意的是:執(zhí)行一個(gè)流式查詢(xún)后荚板,數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)框架就不負(fù)責(zé)關(guān)閉數(shù)據(jù)庫(kù)連接了,需要應(yīng)用在取完數(shù)據(jù)后自己關(guān)閉吩屹。

MyBatis 流式查詢(xún)接口

MyBatis 提供了一個(gè)叫 org.apache.ibatis.cursor.Cursor 的接口類(lèi)用于流式查詢(xún)跪另,這個(gè)接口繼承了 java.io.Closeablejava.lang.Iterable 接口,由此可知:

  1. Cursor 是可關(guān)閉的煤搜;
  2. Cursor 是可遍歷的免绿。

除此之外,Cursor 還提供了三個(gè)方法:

  1. isOpen():用于在取數(shù)據(jù)之前判斷 Cursor 對(duì)象是否是打開(kāi)狀態(tài)擦盾。只有當(dāng)打開(kāi)時(shí) Cursor 才能取數(shù)據(jù)嘲驾;
  2. isConsumed():用于判斷查詢(xún)結(jié)果是否全部取完。
  3. getCurrentIndex():返回已經(jīng)獲取了多少條數(shù)據(jù)

因?yàn)?Cursor 實(shí)現(xiàn)了迭代器接口迹卢,因此在實(shí)際使用當(dāng)中辽故,從 Cursor 取數(shù)據(jù)非常簡(jiǎn)單:

cursor.forEach(rowObject -> {...});  

但構(gòu)建 Cursor 的過(guò)程不簡(jiǎn)單

我們舉個(gè)實(shí)際例子。下面是一個(gè) Mapper 類(lèi):

@Mapper  
public interface FooMapper {  
    @Select("select * from foo limit #{limit}")  
    Cursor<Foo> scan(@Param("limit") int limit);  
}  

方法 scan() 是一個(gè)非常簡(jiǎn)單的查詢(xún)婶希。通過(guò)指定 Mapper 方法的返回值為 Cursor 類(lèi)型榕暇,MyBatis 就知道這個(gè)查詢(xún)方法一個(gè)流式查詢(xún)。

然后我們?cè)賹?xiě)一個(gè) SpringMVC Controller 方法來(lái)調(diào)用 Mapper(無(wú)關(guān)的代碼已經(jīng)省略):

@GetMapping("foo/scan/0/{limit}")  
public void scanFoo0(@PathVariable("limit") int limit) throws Exception {  
    try (Cursor<Foo> cursor = fooMapper.scan(limit)) {  // 1  
        cursor.forEach(foo -> {});                      // 2  
    }  
}  

上面的代碼中喻杈,fooMapper 是 @Autowired 進(jìn)來(lái)的彤枢。注釋 1 處調(diào)用 scan 方法,得到 Cursor 對(duì)象并保證它能最后關(guān)閉筒饰;2 處則是從 cursor 中取數(shù)據(jù)缴啡。

上面的代碼看上去沒(méi)什么問(wèn)題,但是執(zhí)行 scanFoo0() 時(shí)會(huì)報(bào)錯(cuò):

java.lang.IllegalStateException: A Cursor is already closed.

這是因?yàn)槲覀兦懊嬲f(shuō)了在取數(shù)據(jù)的過(guò)程中需要保持?jǐn)?shù)據(jù)庫(kù)連接瓷们,而 Mapper 方法通常在執(zhí)行完后連接就關(guān)閉了业栅,因此 Cusor也一并關(guān)閉了。

所以谬晕,解決這個(gè)問(wèn)題的思路不復(fù)雜碘裕,保持?jǐn)?shù)據(jù)庫(kù)連接打開(kāi)即可。我們至少有三種方案可選攒钳。

方案一:SqlSessionFactory

我們可以用 SqlSessionFactory來(lái)手工打開(kāi)數(shù)據(jù)庫(kù)連接帮孔,將 Controller 方法修改如下:

@GetMapping("foo/scan/1/{limit}")  
public void scanFoo1(@PathVariable("limit") int limit) throws Exception {  
    try (  
        SqlSession sqlSession = sqlSessionFactory.openSession();  // 1  
        Cursor<Foo> cursor =   
              sqlSession.getMapper(FooMapper.class).scan(limit)   // 2  
    ) {  
        cursor.forEach(foo -> { });  
    }  
}  

上面的代碼中,

1 處我們開(kāi)啟了一個(gè) SqlSession (實(shí)際上也代表了一個(gè)數(shù)據(jù)庫(kù)連接),并保證它最后能關(guān)閉

文兢;2 處我們使用 SqlSession 來(lái)獲得 Mapper 對(duì)象晤斩。這樣才能保證得到的 Cursor 對(duì)象是打開(kāi)狀態(tài)的。

方案二:TransactionTemplate

在 Spring 中姆坚,我們可以用 TransactionTemplate 來(lái)執(zhí)行一個(gè)數(shù)據(jù)庫(kù)事務(wù)澳泵,這個(gè)過(guò)程中數(shù)據(jù)庫(kù)連接同樣是打開(kāi)的。代碼如下:

@GetMapping("foo/scan/2/{limit}")  
public void scanFoo2(@PathVariable("limit") int limit) throws Exception {  
    TransactionTemplate transactionTemplate =   
            new TransactionTemplate(transactionManager);  // 1  
  
    transactionTemplate.execute(status -> {               // 2  
        try (Cursor<Foo> cursor = fooMapper.scan(limit)) {  
            cursor.forEach(foo -> { });  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
        return null;  
    });  
}  

上面的代碼中兼呵,

1 處我們創(chuàng)建了一個(gè) TransactionTemplate 對(duì)象(此處transactionManager 是怎么來(lái)的不用多解釋?zhuān)疚募僭O(shè)讀者對(duì) Spring 數(shù)據(jù)庫(kù)事務(wù)的使用比較熟悉了)兔辅,

2 處執(zhí)行數(shù)據(jù)庫(kù)事務(wù),而數(shù)據(jù)庫(kù)事務(wù)的內(nèi)容則是調(diào)用 Mapper 對(duì)象的流式查詢(xún)萍程。注意這里的 Mapper 對(duì)象無(wú)需通過(guò) SqlSession 創(chuàng)建幢妄。

方案三:@Transactional 注解

這個(gè)本質(zhì)上和方案二一樣,代碼如下:

@GetMapping("foo/scan/3/{limit}")  
@Transactional  
public void scanFoo3(@PathVariable("limit") int limit) throws Exception {  
    Cursor<Foo> cursor = fooMapper.scan(limit)
    Iterator<Foo> iterator = cursor.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }  
}  

它僅僅是在原來(lái)方法上面加了個(gè) @Transactional 注解茫负。這個(gè)方案看上去最簡(jiǎn)潔蕉鸳,但請(qǐng)注意 Spring 框架當(dāng)中注解使用的坑:只在外部調(diào)用時(shí)生效。在當(dāng)前類(lèi)中調(diào)用這個(gè)方法忍法,依舊會(huì)報(bào)錯(cuò)潮尝。

以上是三種實(shí)現(xiàn) MyBatis 流式查詢(xún)的方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饿序,一起剝皮案震驚了整個(gè)濱河市勉失,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌原探,老刑警劉巖乱凿,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異咽弦,居然都是意外死亡徒蟆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)型型,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)段审,“玉大人,你說(shuō)我怎么就攤上這事闹蒜∷峦鳎” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵绷落,是天一觀(guān)的道長(zhǎng)姥闪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)砌烁,這世上最難降的妖魔是什么筐喳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上疏唾,老公的妹妹穿的比我還像新娘。我一直安慰自己函似,他們只是感情好槐脏,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著撇寞,像睡著了一般顿天。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蔑担,一...
    開(kāi)封第一講書(shū)人閱讀 50,096評(píng)論 1 291
  • 那天牌废,我揣著相機(jī)與錄音,去河邊找鬼啤握。 笑死鸟缕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的排抬。 我是一名探鬼主播懂从,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蹲蒲!你這毒婦竟也來(lái)了番甩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤届搁,失蹤者是張志新(化名)和其女友劉穎缘薛,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體卡睦,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宴胧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了么翰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牺汤。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖浩嫌,靈堂內(nèi)的尸體忽然破棺而出檐迟,到底是詐尸還是另有隱情,我是刑警寧澤码耐,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布追迟,位于F島的核電站,受9級(jí)特大地震影響骚腥,放射性物質(zhì)發(fā)生泄漏敦间。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望廓块。 院中可真熱鬧厢绝,春花似錦、人聲如沸带猴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拴清。三九已至靶病,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間口予,已是汗流浹背娄周。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沪停,地道東北人煤辨。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像牙甫,于是被迫代替她去往敵國(guó)和親掷酗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容