SpringBoot項目實現(xiàn)MyBatis流式查詢的教程詳解
前言
mybatis的流式查詢,有點冷門,實際用的場景比較少,但是在某些特殊場景下,卻是十分有效的一個方法。很多人沒有聽說過,實際上是對mybatis沒有太重視,對mybatis想法還停留一個dao接口對應著mapper里的一個sql,mybatis的關鍵是如何寫好sql以及sql的優(yōu)化上;
其實mybatis遠不止這些,通過這篇文章,和大家一塊來見識一下流式查詢,mybatis相對冷門的神秘面紗。文章的內(nèi)容將從以下幾個方面展開:
什么是mybatis的流式查詢;
Cursor接口的主要方法;
代碼層面如何實現(xiàn);
具體的應用場景;
使用中的一些注意事項;
環(huán)境 配置
jdk版本:1.8
開發(fā)工具:Intellij iDEA 2020.1
springboot:2.3.9.RELEASE
mybatis-spring-boot-starter:2.1.4
什么是mybatis流式查詢?
使用mybatis作為持久層的框架時,通過mybatis執(zhí)行查詢數(shù)據(jù)的請求執(zhí)行成功后,mybatis返回的結(jié)果集不是一個集合或?qū)ο?,而是一個迭代器,可以通過遍歷迭代器來取出結(jié)果集,避免一次性取出大量的數(shù)據(jù)而占用太多的內(nèi)存。
Cursor
org.apache.ibatis.cursor.Cursor
接口有三個抽象方法,分別是
isOpen() :判斷cursor是否正處于打開狀態(tài);
isConsumed() :判斷查詢結(jié)果是否全部讀取完;
getCurrentIndex() :查詢已讀取數(shù)據(jù)在全部數(shù)據(jù)里的索引位置;
public interface Cursor<T> extends Closeable, Iterable<T> { //判斷cursor是否正處于打開狀態(tài) //當返回true,則表示cursor已經(jīng)開始從數(shù)據(jù)庫里刷新數(shù)據(jù)了; boolean isOpen(); //判斷查詢結(jié)果是否全部讀取完; //當返回true,則表示查詢sql匹配的全部數(shù)據(jù)都消費完了; boolean isConsumed(); //查詢已讀取數(shù)據(jù)在全部數(shù)據(jù)里的索引位置; //第一條數(shù)據(jù)的索引位置為0;當返回索引位置為-1時,則表示已經(jīng)沒有數(shù)據(jù)可以讀??; int getCurrentIndex(); }
代碼實現(xiàn)
mybatis的所謂流式查詢,就是服務端程序查詢數(shù)據(jù)的過程中,與遠程數(shù)據(jù)庫一直保持連接,不斷的去數(shù)據(jù)庫拉取數(shù)據(jù),提交事務并關閉sqlsession后,數(shù)據(jù)庫連接斷開,停止數(shù)據(jù)拉取,需要注意的是使用這種方式,需要自己手動維護sqlsession和事務的提交。
1、實現(xiàn)方式很簡單,原來返回的類型是集合或?qū)ο螅魇讲樵兎祷氐牡念愋虲uror,泛型內(nèi)表示實際的類型,其他沒有變化;
@Mapper public interface PersonDao { Cursor<Person> selectByCursor(); Integer queryCount(); }
<select id="selectByCursor" resultMap="personMap"> select * from sys_person order by id desc </select> <select id="queryCount" resultType="java.lang.Integer"> select count(*) from sys_person </select>
2、dao層向service層返回的是Cursor類型對象,只要不提交關閉sqlsession,服務端程序就可以一直從數(shù)據(jù)數(shù)據(jù)庫讀取數(shù)據(jù),直到查詢sql匹配到數(shù)據(jù)全部讀取完;
示例里的主要業(yè)務邏輯是:從sys_person表中讀取所有的人員信息數(shù)據(jù),然后按照每1000條數(shù)據(jù)為一組,讀取到內(nèi)存里進行處理,以此類推,直到查詢sql匹配到數(shù)據(jù)全部處理完,再提交事務,關閉sqlSession;
@Service @Slf4j public class PersonServiceImpl implements IPersonService { @Autowired private SqlSessionFactory sqlSessionFactory; @Override public void getOneByAsync() throws InterruptedException { new Thread(new Runnable() { @SneakyThrows @Override public void run() { //使用sqlSessionFactory打開一個sqlSession,在沒有讀取完數(shù)據(jù)之前不要提交事務或關閉sqlSession log.info("----開啟sqlSession"); SqlSession sqlSession = sqlSessionFactory.openSession(); try { //獲取到指定mapper PersonDao mapper = sqlSession.getMapper(PersonDao.class); //調(diào)用指定mapper的方法,返回一個cursor Cursor<Person> cursor = mapper.selectByCursor(); //查詢數(shù)據(jù)總量 Integer total = mapper.queryCount(); //定義一個list,用來從cursor中讀取數(shù)據(jù),每讀取夠1000條的時候,開始處理這批數(shù)據(jù); //當前批數(shù)據(jù)處理完之后,清空list,準備接收下一批次數(shù)據(jù);直到大量的數(shù)據(jù)全部處理完; List<Person> personList = new ArrayList<>(); int i = 0; if (cursor != null) { for (Person person : cursor) { if (personList.size() < 1000) { // log.info("----id:{},userName:{}", person.getId(), person.getUserName()); personList.add(person); } else if (personList.size() == 1000) { ++i; log.info("----{}、從cursor取數(shù)據(jù)達到1000條,開始處理數(shù)據(jù)", i); log.info("----處理數(shù)據(jù)中..."); Thread.sleep(1000);//休眠1s模擬處理數(shù)據(jù)需要消耗的時間; log.info("----{}、從cursor中取出的1000條數(shù)據(jù)已經(jīng)處理完畢", i); personList.clear(); personList.add(person); } if (total == (cursor.getCurrentIndex() + 1)) { ++i; log.info("----{}、從cursor取數(shù)據(jù)達到1000條,開始處理數(shù)據(jù)", i); log.info("----處理數(shù)據(jù)中..."); Thread.sleep(1000);//休眠1s模擬處理數(shù)據(jù)需要消耗的時間; log.info("----{}、從cursor中取出的1000條數(shù)據(jù)已經(jīng)處理完畢", i); personList.clear(); } } if (cursor.isConsumed()) { log.info("----查詢sql匹配中的數(shù)據(jù)已經(jīng)消費完畢!"); } } sqlSession.commit(); log.info("----提交事務"); }catch (Exception e){ e.printStackTrace(); sqlSession.rollback(); } finally { if (sqlSession != null) { //全部數(shù)據(jù)讀取并且做好其他業(yè)務操作之后,提交事務并關閉連接; sqlSession.close(); log.info("----關閉sqlSession"); } } } }).start(); } }
應用場景
其實mybatis的流式查詢適用范圍很有限,這里舉個例子,假如有這樣一個需求 :有50萬員工的一年的工資數(shù)據(jù)明細,需要輸出一張公司支出工資的數(shù)據(jù)報表。
需求很簡單,估計有人是這樣想:這太簡單了,查詢出員工的工資數(shù)據(jù)明細,然后按照套上公式逐條計算出結(jié)果,然后匯總計算結(jié)果,插入到新的結(jié)果表里不就行了。事實上這件事絕對不簡單:
50萬的數(shù)據(jù)全部讀取到jvm的內(nèi)存里得占用多大空間?
這么多對象的垃圾回收又需要多久?
這么多數(shù)據(jù)計算是高頻行為還是低步行為?
如果計算到某條員工的數(shù)據(jù)發(fā)生異常,已經(jīng)計算好的數(shù)據(jù)要不要全部回滾?...
總之,直接取出50萬數(shù)據(jù)來計算,風險肯定不小。那怎么辦呢?
在實際的開發(fā)中,也經(jīng)常遇到一些百十萬,說大不大,說小不小的數(shù)據(jù)報表處理,我的主要設計思路通常就是數(shù)據(jù)切隔+異步,具體怎么做呢?結(jié)合上面的例子,是這樣的:
1、按照月份、省份或者部門,對工資明細數(shù)據(jù)進行數(shù)據(jù)切隔分組;
2、把不同月份、省份、部門的工資數(shù)據(jù)包裝成多線程任務,放到線程池中去執(zhí)行;
3、根據(jù)切隔的多線程任務數(shù)量,定義一個同步工具類CountDownLatch;
4、根據(jù)同步工具類CountDownLatch,來判斷所有的多線程任務是否全部執(zhí)行完;等到所有的多線程任務全部執(zhí)行完成后,再執(zhí)行匯總的邏輯;
5、在多線程任務里,查詢具體月份、省份的員工工資數(shù)據(jù)明細的時候,如果數(shù)據(jù)量還是不少,就可以使用mybatis的流式查詢,分批獲取員工工資明細數(shù)據(jù),進行當前批的計算、匯總,然后所有分批數(shù)據(jù)都計算完成后,再匯總所有分批數(shù)據(jù);
注意事項
mybatis的流式查詢的本意,是避免大量數(shù)據(jù)的查詢而導致內(nèi)存溢出,因此dao層查詢返回的是一個迭代器(Cursor),可以每次從迭代器中取出一條查詢結(jié)果,在實際業(yè)務開發(fā)過程中,即是根據(jù)實際的jvm內(nèi)存大小,從迭代器中取出一定數(shù)量的數(shù)據(jù)后,再進行數(shù)據(jù)處理,待處理完之后,繼續(xù)取出一定數(shù)據(jù)再處理,以此類推直到全部數(shù)據(jù)處理完,這樣做的最大好處就是能夠降低內(nèi)存使用和垃圾回收器的負擔,使數(shù)據(jù)處理的過程相對更加高效、可控,內(nèi)存溢出的風險較小;
好處很明顯,缺點也很就明顯,處理的時間可能會變長,需要引入多線程異步操作,并且在迭代器遍歷和數(shù)據(jù)處理的過程中,數(shù)據(jù)庫連接不能斷開,即當前sqlSession要保持持續(xù)打開狀態(tài),一量斷開,數(shù)據(jù)讀取就會中斷,所以關于這塊的處理,使用mybatis原生的sqlSession進行手動查詢、提交事務、回滾和關閉sqlSession最為穩(wěn)妥、最簡單
以上就是SpringBoot項目實現(xiàn)MyBatis流式查詢的教程詳解的詳細內(nèi)容,更多關于SpringBoot MyBatis流式查詢的資料請關注腳本之家其它相關文章!
- SpringBoot或SpringAI對接DeepSeek大模型的詳細步驟
- SpringBoot整合DeepSeek實現(xiàn)AI對話功能
- 在 Spring Boot 3 中接入生成式 AI的操作方法
- 解決創(chuàng)建springboot后啟動報錯:Failed?to?bind?properties?under‘spring.datasource‘
- Springboot項目打包如何將依賴的jar包輸出到指定目錄
- Springboot Logback日志多文件輸出方式(按日期和大小分割)
- Java調(diào)用ChatGPT(基于SpringBoot和Vue)實現(xiàn)可連續(xù)對話和流式輸出的ChatGPT API
- 在Spring Boot中使用Spark Streaming進行實時數(shù)據(jù)處理和流式計算的步驟
- 使用Spring Boot輕松實現(xiàn)流式AI輸出的步驟
相關文章
ByteArrayOutputStream簡介和使用_動力節(jié)點Java學院整理
ByteArrayOutputStream 是字節(jié)數(shù)組輸出流。它繼承于OutputStream。這篇文章主要介紹了ByteArrayOutputStream簡介和使用,需要的朋友可以參考下2017-05-05nas實現(xiàn)java開發(fā)的環(huán)境詳解
這篇文章主要為大家介紹了nas實現(xiàn)java開發(fā)的環(huán)境詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-11-11SpringBoot登錄驗證token攔截器的實現(xiàn)
本文主要介紹了SpringBoot登錄驗證token攔截器的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07解決spring?security?loginProcessingUrl無效問題
這篇文章主要介紹了解決spring?security?loginProcessingUrl無效問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08mybatis實現(xiàn)遍歷Map的key和value
這篇文章主要介紹了mybatis實現(xiàn)遍歷Map的key和value方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01通過實例了解java checked和unchecked異常
這篇文章主要介紹了通過實例了解checked和unchecked異常,Java異常分為兩種類型,checked異常和unchecked異常,另一種叫法是異常和錯誤。下面小編就帶大家來一起學習一下吧2019-06-06