Mybatis流式查詢(xún)并實(shí)現(xiàn)將結(jié)果分批寫(xiě)入文件
Mybatis流式查詢(xún)并將結(jié)果分批寫(xiě)入文件
/**
* 流式查詢(xún),全量導(dǎo)出
*
* @param req 查詢(xún)條件
* @param size 單個(gè)文件數(shù)據(jù)最大條數(shù)
* @return
*/
@ApiOperation(value = "流式查詢(xún),全量導(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;
//文件名稱(chēng)
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á)式訪問(wèn)外部變量有一個(gè)非常重要的限制:變量不可變(只是引用不可變,而不是真正的不可變),AtomicInteger是一個(gè)提供原子操作的Integer類(lèi),通過(guò)線程安全的方式操作加減。
//文件內(nèi)容行數(shù)
AtomicInteger in = new AtomicInteger(1);
//文件名稱(chēng)
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è)定每次流式查詢(xún)的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使用流式查詢(xún)避免數(shù)據(jù)量過(guò)大導(dǎo)致OOM
本文已springboot項(xiàng)目為例,要實(shí)現(xiàn)流式查詢(xú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語(yǔ)句需要增加fetchSize屬性,底層是調(diào)用jdbc的setFetchSize方法,查詢(xún)時(shí)從結(jié)果集里面每次取設(shè)置的行數(shù),循環(huán)去取,直到取完。
默認(rèn)size是0,也就是默認(rèn)會(huì)一次性把結(jié)果集的數(shù)據(jù)全部取出來(lái),當(dāng)結(jié)果集數(shù)據(jù)量很大時(shí)就容易造成內(nèi)存溢出。
<select id="selectGxids" resultType="java.lang.String" fetchSize="1000"> ? ?SELECT gxid from t_gxid ?</select>
自定義ResultHandler來(lái)分批處理結(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) {
? ? // 這里獲取流式查詢(xún)每次返回的單條結(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)用,用來(lái)完成最后一批數(shù)據(jù)處理
? public void end(){
? ? handle();// 處理最后一批不到BATCH_SIZE的數(shù)據(jù)
? }
}serviceImpl類(lèi)中的使用
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é)
非流式查詢(xún):內(nèi)存會(huì)隨著查詢(xún)記錄的增長(zhǎng)而近乎直線增長(zhǎng)。
流式查詢(xún):內(nèi)存會(huì)保持穩(wěn)定,不會(huì)隨著記錄的增長(zhǎng)而增長(zhǎng)。其內(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)文章
解決引用jip-common jar包,報(bào)401 Unauthorized錯(cuò)誤問(wèn)題
這篇文章主要介紹了解決引用jip-common jar包,報(bào)401 Unauthorized錯(cuò)誤問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
java基于控制臺(tái)的學(xué)生學(xué)籍管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java基于控制臺(tái)的學(xué)生學(xué)籍管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
java WebSocket實(shí)現(xiàn)聊天消息推送功能
這篇文章主要為大家詳細(xì)介紹了java WebSocket實(shí)現(xiàn)聊天消息推送功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
Mybatis逆向生成使用擴(kuò)展類(lèi)的實(shí)例代碼詳解
這篇文章主要介紹了Mybatis逆向生成使用擴(kuò)展類(lèi)的實(shí)例代碼,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-05-05
舉例講解Java中synchronized關(guān)鍵字的用法
這篇文章主要介紹了Java中synchronized關(guān)鍵字的用法,針對(duì)synchronized修飾方法的使用作出了簡(jiǎn)單講解和演示,需要的朋友可以參考下2016-04-04
SpringMVC 參數(shù)綁定之視圖傳參到控制器的實(shí)現(xiàn)代碼
這篇文章主要介紹了SpringMVC 參數(shù)綁定之視圖傳參到控制器的相關(guān)知識(shí),本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03
使用JAXBContext輕松實(shí)現(xiàn)Java和xml的互相轉(zhuǎn)換方式
這篇文章主要介紹了依靠JAXBContext輕松實(shí)現(xiàn)Java和xml的互相轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
SpringBoot項(xiàng)目啟動(dòng)報(bào)錯(cuò)踩坑實(shí)戰(zhàn)記錄
這篇文章主要給大家介紹了關(guān)于SpringBoot項(xiàng)目啟動(dòng)報(bào)錯(cuò)踩坑的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-02-02

