SpringBoot查詢(xún)數(shù)據(jù)庫(kù)導(dǎo)出報(bào)表文件方式
一、背景
1、需求
幾千萬(wàn)條報(bào)表數(shù)據(jù)導(dǎo)出到Excel中
2、問(wèn)題
在數(shù)據(jù)量導(dǎo)出不大時(shí),我們的常規(guī)做法是使用MySQL直接查詢(xún)出全部數(shù)據(jù),整理規(guī)劃成Excel列表,使用POI寫(xiě)入到Excel文件中
但是當(dāng)數(shù)據(jù)量較大時(shí),使用MySQL查詢(xún)出所有數(shù)據(jù),一會(huì)超時(shí)斷開(kāi)連接,二會(huì)內(nèi)存溢出,使用POI暫時(shí)不支持分布寫(xiě)入數(shù)據(jù)到Excel中
3、解決
使用數(shù)據(jù)庫(kù)流式讀取可以解決數(shù)據(jù)庫(kù)讀取時(shí)間過(guò)長(zhǎng),內(nèi)存溢出問(wèn)題,這個(gè)解決了一次性讀取全部數(shù)據(jù)到內(nèi)存中
使用CSV文件代替xlsx/xls文件寫(xiě)入,CSV也可以使用Excel打開(kāi)操作,并且也可另存為xlsx/xls,CSV本質(zhì)是文本文件,不用確定尾結(jié)點(diǎn),可以如TXT一樣持續(xù)向文件追加內(nèi)容
二、代碼實(shí)現(xiàn)
1、mapper文件
使用Cursor游標(biāo)標(biāo)識(shí),流式查詢(xún)數(shù)據(jù)數(shù)據(jù)
@Select("<script>" + "select * from `user_report`" + "</script>") Cursor<UserReport> selectCursorByCondition();
2、讀取寫(xiě)入文件
- @Transactional(readOnly = true):搭配數(shù)據(jù)庫(kù)Cursor查詢(xún)使用,事務(wù)注解為只讀,保證數(shù)據(jù)整體的一致性
- fieldArr:字段數(shù)組,規(guī)定要寫(xiě)入文件的字段項(xiàng),比如查詢(xún)的全部字段為a,b,c,d,而我只想顯示a,c,d
- headerArr:字段名數(shù)組,對(duì)應(yīng)文件標(biāo)題欄,需要不字段數(shù)組a,c,d名稱(chēng)一一對(duì)應(yīng)
- CsvUtils:一個(gè)簡(jiǎn)單的工具類(lèi),后續(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"); //查詢(xún)數(shù)據(jù) Cursor<UserReport> modelStream = xxMapper.selectCursorByCondition(); try { PrintWriter out = response.getWriter(); try { //寫(xiě)入標(biāo)題行 out.write(CsvUtils.getTitleLine(headerArr)); //寫(xiě)入數(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工具類(lèi)
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í)體對(duì)象 * @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 { // 獲取對(duì)象對(duì)應(yīng)的Field if (objFiled.contains(field)) { Field objField = srcClass.getDeclaredField(field); objField.setAccessible(true); //設(shè)置private可訪(fǎng)問(wèn) 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é)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java詳解線(xiàn)上內(nèi)存暴漲問(wèn)題定位和解決方案
本篇文章介紹了我在開(kāi)發(fā)過(guò)程中遇到的線(xiàn)上內(nèi)存暴漲的問(wèn)題,以及定位問(wèn)題原因和解決該問(wèn)題的過(guò)程及思路,通讀本篇對(duì)大家的學(xué)習(xí)或工作具有一定的價(jià)值,需要的朋友可以參考下2021-10-10詳解Java數(shù)據(jù)庫(kù)連接JDBC基礎(chǔ)知識(shí)(操作數(shù)據(jù)庫(kù):增刪改查)
這篇文章主要介紹了詳解Java數(shù)據(jù)庫(kù)連接JDBC基礎(chǔ)知識(shí)(操作數(shù)據(jù)庫(kù):增刪改查),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01Springboot項(xiàng)目因?yàn)閗ackson版本問(wèn)題啟動(dòng)報(bào)錯(cuò)解決方案
這篇文章主要介紹了Springboot項(xiàng)目因?yàn)閗ackson版本問(wèn)題啟動(dòng)報(bào)錯(cuò)解決方案,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07