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數(shù)據(jù)庫連接JDBC基礎(chǔ)知識(操作數(shù)據(jù)庫:增刪改查)
這篇文章主要介紹了詳解Java數(shù)據(jù)庫連接JDBC基礎(chǔ)知識(操作數(shù)據(jù)庫:增刪改查),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01
Springboot項目因為kackson版本問題啟動報錯解決方案
這篇文章主要介紹了Springboot項目因為kackson版本問題啟動報錯解決方案,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07

