- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- MyBatis流式查詢(xún)的三種實(shí)現方法
導讀:流式查詢(xún)指的是查詢(xún)成功后不是返回一個(gè)集合而是返回一個(gè)迭代器,應用每次從迭代器取一條查詢(xún)結果。流式查詢(xún)的好處是能夠降低內存使用
如果沒(méi)有流式查詢(xún),我們想要從數據庫取 1000 萬(wàn)條記錄而又沒(méi)有足夠的內存時(shí),就不得不分頁(yè)查詢(xún),而分頁(yè)查詢(xún)效率取決于表設計,如果設計的不好,就無(wú)法執行高效的分頁(yè)查詢(xún)。因此流式查詢(xún)是一個(gè)數據庫訪(fǎng)問(wèn)框架必須具備的功能。
流式查詢(xún)的過(guò)程當中,數據庫連接是保持打開(kāi)狀態(tài)的,因此要注意的是:執行一個(gè)流式查詢(xún)后,數據庫訪(fǎng)問(wèn)框架就不負責關(guān)閉數據庫連接了,需要應用在取完數據后自己關(guān)閉。
MyBatis 提供了一個(gè)叫 org.apache.ibatis.cursor.Cursor 的接口類(lèi)用于流式查詢(xún),這個(gè)接口繼承了 java.io.Closeable 和 java.lang.Iterable 接口,由此可知:
除此之外,Cursor 還提供了三個(gè)方法:
因為 Cursor 實(shí)現了迭代器接口,因此在實(shí)際使用當中,從 Cursor 取數據非常簡(jiǎn)單:
try(Cursor cursor = mapper.querySomeData()) { cursor.forEach(rowObject -> { // ... }); }
使用 try-resource 方式可以令 Cursor 自動(dòng)關(guān)閉。
但構建 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)。我們在定義這個(gè)方時(shí),指定返回值為 Cursor 類(lèi)型,MyBatis 就明白這個(gè)查詢(xún)方法是一個(gè)流式查詢(xún)。
然后我們再寫(xiě)一個(gè) SpringMVC Controller 方法來(lái)調用 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 處是獲取 Cursor 對象并保證它能最后關(guān)閉;2 處則是從 cursor 中取數據。
上面的代碼看上去沒(méi)什么問(wèn)題,但是執行scanFoo0(int)時(shí)會(huì )報錯:
java.lang.IllegalStateException: A Cursor is already closed.
這是因為我們前面說(shuō)了在取數據的過(guò)程中需要保持數據庫連接,而 Mapper 方法通常在執行完后連接就關(guān)閉了,因此 Cusor 也一并關(guān)閉了。
所以,解決這個(gè)問(wèn)題的思路不復雜,保持數據庫連接打開(kāi)即可。我們至少有三種方案可選。
我們可以用 SqlSessionFactory 來(lái)手工打開(kāi)數據庫連接,將 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è)數據庫連接),并保證它最后能關(guān)閉;2 處我們使用 SqlSession 來(lái)獲得 Mapper 對象。這樣才能保證得到的 Cursor 對象是打開(kāi)狀態(tài)的。
在 Spring 中,我們可以用 TransactionTemplate 來(lái)執行一個(gè)數據庫事務(wù),這個(gè)過(guò)程中數據庫連接同樣是打開(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 對象(此處 transactionManager 是怎么來(lái)的不用多解釋?zhuān)疚募僭O讀者對 Spring 數據庫事務(wù)的使用比較熟悉了),2 處執行數據庫事務(wù),而數據庫事務(wù)的內容則是調用 Mapper 對象的流式查詢(xún)。注意這里的 Mapper 對象無(wú)需通過(guò) SqlSession 創(chuàng )建。
這個(gè)本質(zhì)上和方案二一樣,代碼如下:
@GetMapping("foo/scan/3/{limit}") @Transactional public void scanFoo3(@PathVariable("limit") int limit) throws Exception { try (Cursor<Foo> cursor = fooMapper.scan(limit)) { cursor.forEach(foo -> { }); } }
它僅僅是在原來(lái)方法上面加了個(gè) @Transactional 注解。這個(gè)方案看上去最簡(jiǎn)潔,但請注意 Spring 框架當中注解使用的坑:只在外部調用時(shí)生效。在當前類(lèi)中調用這個(gè)方法,依舊會(huì )報錯。
到此這篇關(guān)于MyBatis流式查詢(xún)的實(shí)現方法的文章就介紹到這了,更多相關(guān)MyBatis流式查詢(xún)內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站