SpringBoot查詢數(shù)據(jù)庫導(dǎo)出報表文件方式
一、背景
1、需求
幾千萬條報表數(shù)據(jù)導(dǎo)出到Excel中
2、問題
在數(shù)據(jù)量導(dǎo)出不大時,我們的常規(guī)做法是使用MySQL直接查詢出全部數(shù)據(jù),整理規(guī)劃成Excel列表,使用POI寫入到Excel文件中
但是當(dāng)數(shù)據(jù)量較大時,使用MySQL查詢出所有數(shù)據(jù),一會超時斷開連接,二會內(nèi)存溢出,使用POI暫時不支持分布寫入數(shù)據(jù)到Excel中
3、解決
使用數(shù)據(jù)庫流式讀取可以解決數(shù)據(jù)庫讀取時間過長,內(nèi)存溢出問題,這個解決了一次性讀取全部數(shù)據(jù)到內(nèi)存中
使用CSV文件代替xlsx/xls文件寫入,CSV也可以使用Excel打開操作,并且也可另存為xlsx/xls,CSV本質(zhì)是文本文件,不用確定尾結(jié)點,可以如TXT一樣持續(xù)向文件追加內(nèi)容
二、代碼實現(xiàn)
1、mapper文件
使用Cursor游標(biāo)標(biāo)識,流式查詢數(shù)據(jù)數(shù)據(jù)
@Select("<script>"
+ "select * from `user_report`"
+ "</script>")
Cursor<UserReport> selectCursorByCondition();2、讀取寫入文件
- @Transactional(readOnly = true):搭配數(shù)據(jù)庫Cursor查詢使用,事務(wù)注解為只讀,保證數(shù)據(jù)整體的一致性
- fieldArr:字段數(shù)組,規(guī)定要寫入文件的字段項,比如查詢的全部字段為a,b,c,d,而我只想顯示a,c,d
- headerArr:字段名數(shù)組,對應(yīng)文件標(biāo)題欄,需要不字段數(shù)組a,c,d名稱一一對應(yīng)
- CsvUtils:一個簡單的工具類,后續(xù)貼代碼
/**
* 導(dǎo)出Excel
* @param fieldArr 字段數(shù)組
* @param headerArr 字段名數(shù)組
* @param fileName 文件名
*/
@Transactional(readOnly = true)
public void exportList(HttpServletResponse response,
String[] fieldArr, String[] headerArr, String fileName) {
//設(shè)置文件格式
response.setCharacterEncoding("UTF-8");
response.addHeader("Content-Type", "application/csv");
try {
//文件名 設(shè)置為中文
fileName = new String(fileName.getBytes("gb2312"), "iso8859-1");
} catch (Exception e) {
log.error("file name show error:{}", e.getMessage());
}
//響應(yīng)頭部
response.addHeader("Content-Disposition", "attachment; filename=" + fileName + "(" +
DateUtil.format(DateUtil.date(), DatePattern.NORM_DATETIME_PATTERN) + ").csv");
//查詢數(shù)據(jù)
Cursor<UserReport> modelStream = xxMapper.selectCursorByCondition();
try {
PrintWriter out = response.getWriter();
try {
//寫入標(biāo)題行
out.write(CsvUtils.getTitleLine(headerArr));
//寫入數(shù)據(jù)行
modelStream.forEach(item -> {
out.write(CsvUtils.getRowLine(fieldArr, item));
});
out.flush();
} catch (Exception e) {
log.error("write file error:{}", e.getMessage());
} finally {
out.close();
}
} catch (Exception e) {
log.error("exportList error:{}", e.getMessage());
}
}3、CsvUtils工具類
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
/**
* @Author: catcoder
* @Desc:
* @Time: 10:05 2021/8/2
**/
public class CsvUtils {
/**
* CSV文件列分隔符
*/
public static final String CSV_COLUMN_SEPARATOR = ",";
/**
* CSV文件行分隔符
*/
public static final String CSV_ROW_SEPARATOR = System.lineSeparator();
/**
* 獲取標(biāo)題行
*
* @param headerArr 標(biāo)題數(shù)組
* @return
*/
public static String getTitleLine(String[] headerArr) {
StringBuffer line = new StringBuffer("");
for (String title : headerArr) {
line.append(title).append(CSV_COLUMN_SEPARATOR); //添加標(biāo)題行數(shù)據(jù)
}
line.append(CSV_ROW_SEPARATOR); //換行數(shù)據(jù)
return line.toString();
}
/**
* 或去數(shù)據(jù)行
*
* @param fieldArr 字段數(shù)組
* @param obj 實體對象
* @return
*/
public static String getRowLine(String[] fieldArr, Object obj) {
StringBuffer line = new StringBuffer("");
Class<?> srcClass = obj.getClass();
//獲取Obj 所有字段
Set<String> objFiled = new HashSet<>();
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
objFiled.add(field.getName());
}
for (String field : fieldArr) {
try {
// 獲取對象對應(yīng)的Field
if (objFiled.contains(field)) {
Field objField = srcClass.getDeclaredField(field);
objField.setAccessible(true); //設(shè)置private可訪問
Object value = objField.get(obj);
line.append(value); //添加元素
}
} catch (Exception e) {
e.printStackTrace();
} finally {
line.append(CSV_COLUMN_SEPARATOR); //CSV間隔數(shù)據(jù)
}
}
line.append(CSV_ROW_SEPARATOR);//換行數(shù)據(jù)
return line.toString();
}
}總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java實現(xiàn)飛機大戰(zhàn)游戲?附完整源碼
這篇文章主要介紹了Java實現(xiàn)飛機大戰(zhàn)游戲,本文給大家分享完整源代碼和效果圖展示,對java飛機大戰(zhàn)游戲?qū)崿F(xiàn)代碼感興趣的朋友一起看看吧2022-05-05
Spring mvc整合mybatis(crud+分頁插件)操作mysql
這篇文章主要介紹了Spring mvc整合mybatis(crud+分頁插件)操作mysql的步驟詳解,需要的朋友可以參考下2017-04-04
Mybatis調(diào)用SQL?Server存儲過程的實現(xiàn)示例
在軟件開發(fā)過程中,經(jīng)常會使用到存儲過程,本文就來介紹一下Mybatis調(diào)用SQL?Server存儲過程的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下2024-01-01
SpringBoot接口或方法進行失敗重試的實現(xiàn)方式
為了防止網(wǎng)絡(luò)抖動,影響我們核心接口或方法的成功率,通常我們會對核心方法進行失敗重試,如果我們自己通過for循環(huán)實現(xiàn),會使代碼顯得比較臃腫,所以本文給大家介紹了SpringBoot接口或方法進行失敗重試的實現(xiàn)方式,需要的朋友可以參考下2024-07-07
win7 64位系統(tǒng)JDK安裝配置環(huán)境變量教程
這篇文章主要為大家詳細(xì)介紹了win7 64位系統(tǒng)JDK安裝配置環(huán)境變量教程,感興趣的小伙伴們可以參考一下2016-06-06
java設(shè)計模式之外觀模式學(xué)習(xí)筆記
這篇文章主要為大家詳細(xì)介紹了java設(shè)計模式之外觀模式學(xué)習(xí)筆記,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10

