什么是mybatis流式查詢?
使用mybatis作為持久層的框架時,通過mybatis執行查詢數據的請求執行成功后,mybatis返回的結果集不是一個集合或對象,而是一個迭代器,可以通過遍歷迭代器來取出結果集,避免一次性取出大量的數據而占用太多的內存。
org.apache.ibatis.cursor.Cursor接口有三個抽象方法,分別是
isOpen() :判斷cursor是否正處于打開狀態;
isConsumed() :判斷查詢結果是否全部讀取完;
getCurrentIndex() :查詢已讀取數據在全部數據里的索引位置;
publicinterfaceCursorextendsCloseable,Iterable { //判斷cursor是否正處于打開狀態 //當返回true,則表示cursor已經開始從數據庫里刷新數據了; booleanisOpen(); //判斷查詢結果是否全部讀取完; //當返回true,則表示查詢sql匹配的全部數據都消費完了; booleanisConsumed(); //查詢已讀取數據在全部數據里的索引位置; //第一條數據的索引位置為0;當返回索引位置為-1時,則表示已經沒有數據可以讀取; intgetCurrentIndex(); }
代碼實現
mybatis的所謂流式查詢,就是服務端程序查詢數據的過程中,與遠程數據庫一直保持連接,不斷的去數據庫拉取數據,提交事務并關閉sqlsession后,數據庫連接斷開,停止數據拉取,需要注意的是使用這種方式,需要自己手動維護sqlsession和事務的提交。
1、實現方式很簡單,原來返回的類型是集合或對象,流式查詢返回的的類型Curor,泛型內表示實際的類型,其他沒有變化;
@Mapper publicinterfacePersonDao{ CursorselectByCursor(); IntegerqueryCount(); }
2、dao層向service層返回的是Cursor類型對象,只要不提交關閉sqlsession,服務端程序就可以一直從數據數據庫讀取數據,直到查詢sql匹配到數據全部讀取完;
示例里的主要業務邏輯是:從sys_person表中讀取所有的人員信息數據,然后按照每1000條數據為一組,讀取到內存里進行處理,以此類推,直到查詢sql匹配到數據全部處理完,再提交事務,關閉sqlSession;
@Service @Slf4j publicclassPersonServiceImplimplementsIPersonService{ @Autowired privateSqlSessionFactorysqlSessionFactory; @Override publicvoidgetOneByAsync()throwsInterruptedException{ newThread(newRunnable(){ @SneakyThrows @Override publicvoidrun(){ //使用sqlSessionFactory打開一個sqlSession,在沒有讀取完數據之前不要提交事務或關閉sqlSession log.info("----開啟sqlSession"); SqlSessionsqlSession=sqlSessionFactory.openSession(); try{ //獲取到指定mapper PersonDaomapper=sqlSession.getMapper(PersonDao.class); //調用指定mapper的方法,返回一個cursor Cursorcursor=mapper.selectByCursor(); //查詢數據總量 Integertotal=mapper.queryCount(); //定義一個list,用來從cursor中讀取數據,每讀取夠1000條的時候,開始處理這批數據; //當前批數據處理完之后,清空list,準備接收下一批次數據;直到大量的數據全部處理完; List personList=newArrayList<>(); inti=0; if(cursor!=null){ for(Personperson: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取數據達到1000條,開始處理數據",?i); ?????????????????????????????????log.info("----處理數據中..."); ?????????????????????????????????Thread.sleep(1000);//休眠1s模擬處理數據需要消耗的時間; ?????????????????????????????????log.info("----{}、從cursor中取出的1000條數據已經處理完畢",?i); ?????????????????????????????????personList.clear(); ?????????????????????????????????personList.add(person); ?????????????????????????????} ?????????????????????????????if?(total?==?(cursor.getCurrentIndex()?+?1))?{ ?????????????????????????????????++i; ?????????????????????????????????log.info("----{}、從cursor取數據達到1000條,開始處理數據",?i); ?????????????????????????????????log.info("----處理數據中..."); ?????????????????????????????????Thread.sleep(1000);//休眠1s模擬處理數據需要消耗的時間; ?????????????????????????????????log.info("----{}、從cursor中取出的1000條數據已經處理完畢",?i); ?????????????????????????????????personList.clear(); ?????????????????????????????} ?????????????????????????} ?????????????????????????if?(cursor.isConsumed())?{ ?????????????????????????????log.info("----查詢sql匹配中的數據已經消費完畢!"); ?????????????????????????} ?????????????????????} ?????????????????????sqlSession.commit(); ?????????????????????log.info("----提交事務"); ?????????????????}catch?(Exception?e){ ?????????????????????e.printStackTrace(); ?????????????????????sqlSession.rollback(); ?????????????????} ?????????????????finally?{ ?????????????????????if?(sqlSession?!=?null)?{ ?????????????????????????//全部數據讀取并且做好其他業務操作之后,提交事務并關閉連接; ?????????????????????????sqlSession.close(); ?????????????????????????log.info("----關閉sqlSession");?? ?????????????????????} ?????????????????} ???????????????? ????????????} ????????}).start(); ????} }
應用場景
其實mybatis的流式查詢適用范圍很有限,這里舉個例子,假如有這樣一個需求 :有50萬員工的一年的工資數據明細,需要輸出一張公司支出工資的數據報表。
需求很簡單,估計有人是這樣想:這太簡單了,查詢出員工的工資數據明細,然后按照套上公式逐條計算出結果,然后匯總計算結果,插入到新的結果表里不就行了。事實上這件事絕對不簡單:
50萬的數據全部讀取到jvm的內存里得占用多大空間?
這么多對象的垃圾回收又需要多久?
這么多數據計算是高頻行為還是低步行為?
如果計算到某條員工的數據發生異常,已經計算好的數據要不要全部回滾?...
總之,直接取出50萬數據來計算,風險肯定不小。那怎么辦呢?
在實際的開發中,也經常遇到一些百十萬,說大不大,說小不小的數據報表處理,我的主要設計思路通常就是數據切隔+異步,具體怎么做呢?結合上面的例子,是這樣的:
1、按照月份、省份或者部門,對工資明細數據進行數據切隔分組;
2、把不同月份、省份、部門的工資數據包裝成多線程任務,放到線程池中去執行;
3、根據切隔的多線程任務數量,定義一個同步工具類CountDownLatch;
4、根據同步工具類CountDownLatch,來判斷所有的多線程任務是否全部執行完;等到所有的多線程任務全部執行完成后,再執行匯總的邏輯;
5、在多線程任務里,查詢具體月份、省份的員工工資數據明細的時候,如果數據量還是不少,就可以使用mybatis的流式查詢,分批獲取員工工資明細數據,進行當前批的計算、匯總,然后所有分批數據都計算完成后,再匯總所有分批數據;
注意事項
mybatis的流式查詢的本意,是避免大量數據的查詢而導致內存溢出,因此dao層查詢返回的是一個迭代器(Cursor),可以每次從迭代器中取出一條查詢結果,在實際業務開發過程中,即是根據實際的jvm內存大小,從迭代器中取出一定數量的數據后,再進行數據處理,待處理完之后,繼續取出一定數據再處理,以此類推直到全部數據處理完,這樣做的最大好處就是能夠降低內存使用和垃圾回收器的負擔,使數據處理的過程相對更加高效、可控,內存溢出的風險較小;
好處很明顯,缺點也很就明顯,處理的時間可能會變長,需要引入多線程異步操作,并且在迭代器遍歷和數據處理的過程中,數據庫連接不能斷開,即當前sqlSession要保持持續打開狀態,一量斷開,數據讀取就會中斷,所以關于這塊的處理,使用mybatis原生的sqlSession進行手動查詢、提交事務、回滾和關閉sqlSession最為穩妥、最簡單。
審核編輯:劉清
-
迭代器
+關注
關注
0文章
43瀏覽量
4302 -
SpringBoot
+關注
關注
0文章
173瀏覽量
167
原文標題:SpringBoot+Mybatis 如何實現流式查詢,你知道嗎?
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論