Mybatis流式查詢并實(shí)現(xiàn)將結(jié)果分批寫入文件
Mybatis流式查詢并將結(jié)果分批寫入文件
/** * 流式查詢,全量導(dǎo)出 * * @param req 查詢條件 * @param size 單個(gè)文件數(shù)據(jù)最大條數(shù) * @return */ @ApiOperation(value = "流式查詢,全量導(dǎo)出") @GetMapping("/streamAll") public BaseResultModel streamAll(ReqBillRecordBackQuery req, Integer size) { try { billRecordBackService.streamAll(req, size); } catch (Exception e) { throw new RuntimeException(e); } return BaseResultModel.success(); }
以xml的方式
@Override @Transactional public void streamAll(ReqBillRecordBackQuery req, Integer size) throws Exception { exportXml(req,size); } private void exportXml(ReqBillRecordBackQuery req, Integer size) throws Exception{ //文件內(nèi)容行數(shù) Integer in = 0; //文件名稱 Integer fileName=0; String name = "exportTest"; String suf =".txt"; String path = "H:\\新建文件夾\\新建文件夾\\export\\"; File ff = new File(path); //遞歸刪除目錄中的所有文件和子目錄,而不刪除目錄本身。 FileUtils.cleanDirectory(ff); File fe = new File(path+name+fileName+suf); //刪除此抽象路徑名表示的文件或目錄 //mkdirs()可以建立多級(jí)文件夾, mkdir()只會(huì)建立一級(jí)的文件夾 // fe.mkdirs(); //獲取文件輸出列 BufferedWriter bufferedWriter=new BufferedWriter(new FileWriter(fe)); StringBuilder sb = new StringBuilder(); Cursor<BillRecordBack> billRecordBacks = mapper.streamAll(req); for (BillRecordBack bil : billRecordBacks) { sb.append(bil).append("\n"); in++; if (in>=size){ in=0; fileName++; fe = new File(path+name+fileName+suf); bufferedWriter = new BufferedWriter(new FileWriter(fe)); } bufferedWriter.write(sb.toString()); //將StringBuilder數(shù)據(jù)重置 sb.setLength(0); } //最后需要自己關(guān)閉流 billRecordBacks.close(); bufferedWriter.close(); }
<select id="streamAll" resultType="com.psh.hik.entity.BillRecordBack" fetchSize="5000"> select t_id,r_id,r_time,r_number,descd,deleted,ctime,crname,mtime,chname from bill_record_back <where> <if test="null != param.rTime and ''!= param.rTime"> ctime = #{param.rTime} </if> <if test="null != param.rNumber and ''!= param.rNumber"> ctime = #{param.rNumber} </if> </where> </select>
以mybatis-plus的方式
private void exportNote(ReqBillRecordBackQuery req, Integer size) throws Exception{ //lambda表達(dá)式訪問外部變量有一個(gè)非常重要的限制:變量不可變(只是引用不可變,而不是真正的不可變),AtomicInteger是一個(gè)提供原子操作的Integer類,通過線程安全的方式操作加減。 //文件內(nèi)容行數(shù) AtomicInteger in = new AtomicInteger(1); //文件名稱 AtomicInteger fileName= new AtomicInteger(0); String name = "exportTest"; String suf =".txt"; String path = "H:\\新建文件夾\\新建文件夾\\export\\"; File ff = new File(path); //遞歸刪除目錄中的所有文件和子目錄,而不刪除目錄本身。 FileUtils.cleanDirectory(ff); AtomicReference<File> fe = new AtomicReference<>(new File(path + name + fileName + suf)); AtomicReference<BufferedWriter> bufferedWriter= new AtomicReference<>(new BufferedWriter(new FileWriter(fe.get()))); StringBuilder sb = new StringBuilder(); mapper.exportNote(req,resultContext -> { try { if (fileName.get()>=20){ return; } BillRecordBack resultObject = resultContext.getResultObject(); sb.append(resultObject).append("\n"); //a.incrementAndGet(); 先+1,再返回,a.getAndIncrement()先返回,再 +1 in.getAndIncrement(); System.out.println(in); if (in.get() >=size){ in.set(0); fileName.getAndIncrement(); fe.set(new File(path + name + fileName + suf)); bufferedWriter.set(new BufferedWriter(new FileWriter(fe.get()))); } bufferedWriter.get().write(sb.toString()); //將StringBuilder數(shù)據(jù)重置 sb.setLength(0); }catch (Exception e){ throw new RuntimeException(e); } }); bufferedWriter.get().close(); }
@Select("select t_id,r_id,r_time,r_number,descd,deleted,ctime,crname,mtime,chname from bill_record_back") //這個(gè)注解是設(shè)定每次流式查詢的iterator大小的,這里是1000條 ,ResultSetType.FORWARD_ONLY 只允許游標(biāo)向下移動(dòng) @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 5000) @ResultType(BillRecordBack.class) void exportNote(ReqBillRecordBackQuery req, ResultHandler<BillRecordBack> handler);
Mybatis使用流式查詢避免數(shù)據(jù)量過大導(dǎo)致OOM
本文已springboot項(xiàng)目為例,要實(shí)現(xiàn)流式查詢需要完成以下幾步
POM文件中的配置
springboot中整合mybatis
<dependency> ? <groupId>org.mybatis.spring.boot</groupId> ? <artifactId>mybatis-spring-boot-starter</artifactId> ? <version>1.1.1</version> </dependency>
mapper.xml文件配置
select語句需要增加fetchSize屬性,底層是調(diào)用jdbc的setFetchSize方法,查詢時(shí)從結(jié)果集里面每次取設(shè)置的行數(shù),循環(huán)去取,直到取完。
默認(rèn)size是0,也就是默認(rèn)會(huì)一次性把結(jié)果集的數(shù)據(jù)全部取出來,當(dāng)結(jié)果集數(shù)據(jù)量很大時(shí)就容易造成內(nèi)存溢出。
<select id="selectGxids" resultType="java.lang.String" fetchSize="1000"> ? ?SELECT gxid from t_gxid ?</select>
自定義ResultHandler來分批處理結(jié)果集
package flowselect; import org.apache.ibatis.session.ResultContext; import org.apache.ibatis.session.ResultHandler; import java.util.Set; public class GxidResultHandler implements ResultHandler<String> { ? // 這是每批處理的大小 ? private final static int BATCH_SIZE = 1000; ? private int size; ? // 存儲(chǔ)每批數(shù)據(jù)的臨時(shí)容器 ? private Set<String> gxids; ? public void handleResult(ResultContext<? extends String> resultContext) { ? ? // 這里獲取流式查詢每次返回的單條結(jié)果 ? ? String gxid = resultContext.getResultObject(); ? ? // 你可以看自己的項(xiàng)目需要分批進(jìn)行處理或者單個(gè)處理,這里以分批處理為例 ? ? gxids.add(gxid); ? ? size++; ? ? if (size == BATCH_SIZE) { ? ? ? handle(); ? ? } ? } ? private void handle() { ? ? try { ? ? ? // 在這里可以對(duì)你獲取到的批量結(jié)果數(shù)據(jù)進(jìn)行需要的業(yè)務(wù)處理 ? ? } finally { ? ? ? // 處理完每批數(shù)據(jù)后后將臨時(shí)清空 ? ? ? size = 0; ? ? ? gxids.clear(); ? ? } ? } ? // 這個(gè)方法給外面調(diào)用,用來完成最后一批數(shù)據(jù)處理 ? public void end(){ ? ? handle();// 處理最后一批不到BATCH_SIZE的數(shù)據(jù) ? } }
serviceImpl類中的使用
package flowselect; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.beans.factory.annotation.Autowired; @Service public class ServiceImpl implements Service { ? @Autowired ? SqlSessionTemplate sqlSessionTemplate; ? public void method(){ ? ? GxidResultHandler gxidResultHandler = new GxidResultHandler(); ? ? sqlSessionTemplate.select("flowselect.Mapper.selectGxids", gxidResultHandler); ? ? gxidResultHandler.end(); ? } }
總結(jié)
非流式查詢:內(nèi)存會(huì)隨著查詢記錄的增長而近乎直線增長。
流式查詢:內(nèi)存會(huì)保持穩(wěn)定,不會(huì)隨著記錄的增長而增長。其內(nèi)存大小取決于批處理大小BATCH_SIZE的設(shè)置,該尺寸越大,內(nèi)存會(huì)越大。所以BATCH_SIZE應(yīng)該根據(jù)業(yè)務(wù)情況設(shè)置合適的大小。
另外要切記每次處理完一批結(jié)果要記得釋放存儲(chǔ)每批數(shù)據(jù)的臨時(shí)容器,即上文中的gxids.clear();
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
淺談web服務(wù)器項(xiàng)目中靜態(tài)請(qǐng)求和動(dòng)態(tài)請(qǐng)求處理
這篇文章主要介紹了淺談web服務(wù)器項(xiàng)目中靜態(tài)請(qǐng)求和動(dòng)態(tài)請(qǐng)求處理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07Java遞歸遍歷樹形結(jié)構(gòu)的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java遞歸遍歷樹形結(jié)構(gòu)的相關(guān)資料,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2016-03-03Spring中的@ExceptionHandler異常攔截器
這篇文章主要介紹了Spring中的@ExceptionHandler異常攔截器,Spring的@ExceptionHandler可以用來統(tǒng)一處理方法拋出的異常,給方法加上@ExceptionHandler注解,這個(gè)方法就會(huì)處理類中其他方法拋出的異常,需要的朋友可以參考下2024-01-01MyBatis的CRUD中的不同參數(shù)綁定查詢實(shí)現(xiàn)
本文主要介紹了MyBatis的CRUD中的不同參數(shù)綁定查詢實(shí)現(xiàn),主要包括單個(gè)參數(shù)傳遞綁定,序號(hào)參數(shù)傳遞綁定,注解參數(shù)傳遞綁定,pojo(對(duì)象)參數(shù)傳遞綁定,map參數(shù)傳遞綁定這幾種類型,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12淺析mybatis和spring整合的實(shí)現(xiàn)過程
據(jù)官方的說法,在Mybatis3問世之前,Spring3的開發(fā)工作就已經(jīng)完成了,所以Spring3中還是沒有對(duì)Mybatis3的支持。因此由Mybatis社區(qū)自己開發(fā)了一個(gè)Mybatis-Spring用來滿足Mybatis用戶整合Spring的需求,下面通過Mybatis-Spring來整合Mybatis跟Spring的用法做介紹2015-10-10IntelliJ IDEA創(chuàng)建普通的Java 項(xiàng)目及創(chuàng)建 Java 文件并運(yùn)行的教程
這篇文章主要介紹了IntelliJ IDEA創(chuàng)建普通的Java 項(xiàng)目及創(chuàng)建 Java 文件并運(yùn)行的教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02