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é)點(diǎn),可以如TXT一樣持續(xù)向文件追加內(nèi)容
二、代碼實(shí)現(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ī)定要寫入文件的字段項(xiàng),比如查詢的全部字段為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 實(shí)體對象 * @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)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Java數(shù)據(jù)庫連接JDBC基礎(chǔ)知識(操作數(shù)據(jù)庫:增刪改查)
這篇文章主要介紹了詳解Java數(shù)據(jù)庫連接JDBC基礎(chǔ)知識(操作數(shù)據(jù)庫:增刪改查),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01Springboot項(xiàng)目因?yàn)閗ackson版本問題啟動報錯解決方案
這篇文章主要介紹了Springboot項(xiàng)目因?yàn)閗ackson版本問題啟動報錯解決方案,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07