Mybatis流式查詢并實現(xiàn)將結(jié)果分批寫入文件
Mybatis流式查詢并將結(jié)果分批寫入文件
/**
* 流式查詢,全量導(dǎo)出
*
* @param req 查詢條件
* @param size 單個文件數(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()可以建立多級文件夾, mkdir()只會建立一級的文件夾
// 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á)式訪問外部變量有一個非常重要的限制:變量不可變(只是引用不可變,而不是真正的不可變),AtomicInteger是一個提供原子操作的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")
//這個注解是設(shè)定每次流式查詢的iterator大小的,這里是1000條 ,ResultSetType.FORWARD_ONLY 只允許游標(biāo)向下移動
@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à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方法,查詢時從結(jié)果集里面每次取設(shè)置的行數(shù),循環(huán)去取,直到取完。
默認(rèn)size是0,也就是默認(rèn)會一次性把結(jié)果集的數(shù)據(jù)全部取出來,當(dāng)結(jié)果集數(shù)據(jù)量很大時就容易造成內(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;
? // 存儲每批數(shù)據(jù)的臨時容器
? private Set<String> gxids;
? public void handleResult(ResultContext<? extends String> resultContext) {
? ? // 這里獲取流式查詢每次返回的單條結(jié)果
? ? String gxid = resultContext.getResultObject();
? ? // 你可以看自己的項目需要分批進行處理或者單個處理,這里以分批處理為例
? ? gxids.add(gxid);
? ? size++;
? ? if (size == BATCH_SIZE) {
? ? ? handle();
? ? }
? }
? private void handle() {
? ? try {
? ? ? // 在這里可以對你獲取到的批量結(jié)果數(shù)據(jù)進行需要的業(yè)務(wù)處理
? ? } finally {
? ? ? // 處理完每批數(shù)據(jù)后后將臨時清空
? ? ? size = 0;
? ? ? gxids.clear();
? ? }
? }
? // 這個方法給外面調(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)存會隨著查詢記錄的增長而近乎直線增長。
流式查詢:內(nèi)存會保持穩(wěn)定,不會隨著記錄的增長而增長。其內(nèi)存大小取決于批處理大小BATCH_SIZE的設(shè)置,該尺寸越大,內(nèi)存會越大。所以BATCH_SIZE應(yīng)該根據(jù)業(yè)務(wù)情況設(shè)置合適的大小。
另外要切記每次處理完一批結(jié)果要記得釋放存儲每批數(shù)據(jù)的臨時容器,即上文中的gxids.clear();
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
淺談web服務(wù)器項目中靜態(tài)請求和動態(tài)請求處理
這篇文章主要介紹了淺談web服務(wù)器項目中靜態(tài)請求和動態(tài)請求處理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Java遞歸遍歷樹形結(jié)構(gòu)的實現(xiàn)代碼
這篇文章主要介紹了Java遞歸遍歷樹形結(jié)構(gòu)的相關(guān)資料,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2016-03-03
Spring中的@ExceptionHandler異常攔截器
這篇文章主要介紹了Spring中的@ExceptionHandler異常攔截器,Spring的@ExceptionHandler可以用來統(tǒng)一處理方法拋出的異常,給方法加上@ExceptionHandler注解,這個方法就會處理類中其他方法拋出的異常,需要的朋友可以參考下2024-01-01
MyBatis的CRUD中的不同參數(shù)綁定查詢實現(xiàn)
本文主要介紹了MyBatis的CRUD中的不同參數(shù)綁定查詢實現(xiàn),主要包括單個參數(shù)傳遞綁定,序號參數(shù)傳遞綁定,注解參數(shù)傳遞綁定,pojo(對象)參數(shù)傳遞綁定,map參數(shù)傳遞綁定這幾種類型,具有一定的參考價值,感興趣的可以了解一下2023-12-12
IntelliJ IDEA創(chuàng)建普通的Java 項目及創(chuàng)建 Java 文件并運行的教程
這篇文章主要介紹了IntelliJ IDEA創(chuàng)建普通的Java 項目及創(chuàng)建 Java 文件并運行的教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02

