最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot導(dǎo)出Excel的最佳實踐

 更新時間:2026年04月26日 14:12:37   作者:九轉(zhuǎn)成圣  
在企業(yè)級后臺管理系統(tǒng)的開發(fā)中,導(dǎo)出 Excel絕對是一個高頻且讓人又愛又恨的需求,初入職場時,我們可能都會手寫原生的Apache POI代碼,為了解放生產(chǎn)力,我們必須對Excel導(dǎo)出進行通用化封裝,本文將為你提供業(yè)界最主流的兩套解決方案,需要的朋友可以參考下

引言:為什么我們需要封裝 Excel 導(dǎo)出?

在企業(yè)級后臺管理系統(tǒng)的開發(fā)中,“導(dǎo)出 Excel” 絕對是一個高頻且讓人又愛又恨的需求。

初入職場時,我們可能都會手寫原生的 Apache POI 代碼:創(chuàng)建 Workbook,創(chuàng)建 Sheet,寫死大標(biāo)題,然后用兩層 for 循環(huán)把數(shù)據(jù)一行一行塞進單元格。

這種做法在前期看似簡單,但隨著業(yè)務(wù)線的發(fā)展,你會遇到以下幾個致命痛點:

  1. 重復(fù)勞動: 每次新增一個報表,都要復(fù)制粘貼幾百行高度相似的 POI 代碼,僅僅是改了改表頭和字段。
  2. 字典翻譯極其痛苦: 數(shù)據(jù)庫存的是 type = 1,導(dǎo)出時要變成“收入”;存的是時間戳,導(dǎo)出時要格式化。如果在 SQL 里連表翻譯,會拖慢查詢效率;如果在 Java 循環(huán)里寫 if-else,代碼會變得又長又臭。
  3. 內(nèi)存溢出(OOM)風(fēng)險: 如果直接使用 XSSFWorkbook 導(dǎo)出幾十萬條數(shù)據(jù),內(nèi)存瞬間打滿。即使用了基于磁盤流的 SXSSFWorkbook,手動管理的邏輯依然繁雜。

為了解放生產(chǎn)力,我們必須對 Excel 導(dǎo)出進行通用化封裝。本文將為你提供業(yè)界最主流的兩套解決方案:無依賴侵入的 Java 8 函數(shù)式封裝,以及阿里開源神器 EasyExcel 的接入指南。

方案一:基于 Java 8 Function 的極致輕量級封裝

如果你不想引入龐大的第三方框架,或者項目要求嚴(yán)格控制依賴,那么原生的 poi-ooxml 配合 Java 8 的函數(shù)式接口(Function)是絕佳的選擇。

痛點突圍:如何避免使用反射?

很多傳統(tǒng)封裝喜歡用 Java 反射來讀取對象屬性,但這無法優(yōu)雅地解決“字典翻譯”和“復(fù)雜格式化”的問題。利用 Function<T, Object>,我們可以把“如何提取數(shù)據(jù)”和“如何轉(zhuǎn)換數(shù)據(jù)”的動作,作為參數(shù)傳給工具類。

核心工具類代碼ExcelExportUtil

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.util.List;
import java.util.function.Function;
/**
 * 通用 Excel 導(dǎo)出工具類
 */
public class ExcelExportUtil {
    /**
     * 核心導(dǎo)出方法
     * @param response   HttpServletResponse
     * @param fileName   文件名
     * @param title      表格大標(biāo)題(傳空則不生成)
     * @param headers    表頭集合
     * @param dataList   數(shù)據(jù)集合 List<T>
     * @param extractors 數(shù)據(jù)提取規(guī)則集合,支持動態(tài)翻譯和格式化
     */
    public static <T> void export(HttpServletResponse response, 
                                  String fileName, 
                                  String title, 
                                  List<String> headers, 
                                  List<T> dataList, 
                                  List<Function<T, Object>> extractors) throws Exception {
        if (headers.size() != extractors.size()) {
            throw new IllegalArgumentException("表頭列數(shù)與數(shù)據(jù)提取規(guī)則數(shù)量不一致!");
        }
        // 使用 SXSSFWorkbook 防 OOM,內(nèi)存僅保留 100 行
        SXSSFWorkbook workbook = new SXSSFWorkbook(100);
        Sheet sheet = workbook.createSheet("Sheet1");
        int rowIndex = 0;
        // 1. 生成大標(biāo)題 (可選)
        if (title != null && !title.isEmpty()) {
            Row titleRow = sheet.createRow(rowIndex++);
            Cell titleCell = titleRow.createCell(0);
            titleCell.setCellValue(title);
            sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, headers.size() - 1));
        }
        // 2. 生成表頭
        Row headerRow = sheet.createRow(rowIndex++);
        for (int i = 0; i < headers.size(); i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers.get(i));
            sheet.setColumnWidth(i, 15 * 256); // 簡單統(tǒng)一列寬
        }
        // 3. 填充數(shù)據(jù)(核心亮點:使用 Function 動態(tài)取值并轉(zhuǎn)換)
        for (T data : dataList) {
            Row row = sheet.createRow(rowIndex++);
            for (int i = 0; i < extractors.size(); i++) {
                Cell cell = row.createCell(i);
                // 執(zhí)行外部傳入的 Lambda 表達式
                Object value = extractors.get(i).apply(data);
                if (value != null) {
                    if (value instanceof Number) {
                        cell.setCellValue(((Number) value).doubleValue());
                    } else {
                        cell.setCellValue(value.toString());
                    }
                } else {
                    cell.setCellValue("");
                }
            }
        }
        // 4. 寫出響應(yīng)流
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName + ".xlsx");
        try {
            workbook.write(response.getOutputStream());
        } finally {
            workbook.dispose(); // 清理磁盤臨時文件
            workbook.close();
        }
    }
}

實戰(zhàn)調(diào)用:爽到飛起的 Controller

有了這個工具類,Controller 層的代碼變得極其清爽,甚至可以在一行代碼內(nèi)完成復(fù)雜的字典翻譯和日期格式化。

@PostMapping("/exportFinanceRecord")
public void exportFinanceRecord(HttpServletResponse response, @RequestBody FinanceQuery param) throws Exception {
    List<FinanceRecord> recordList = financeRecordService.list(param);
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    // 定義表頭
    List<String> headers = Arrays.asList("記錄ID", "收支類型", "金額", "創(chuàng)建時間");

    // 定義數(shù)據(jù)提取與轉(zhuǎn)換規(guī)則(利用 Lambda 表達式)
    List<Function<FinanceRecord, Object>> extractors = Arrays.asList(
        FinanceRecord::getId, 
        record -> record.getType() == 1 ? "收入" : "支出", // 完美解決字典翻譯
        FinanceRecord::getAmount,
        record -> record.getCreateTime() != null ? sdf.format(record.getCreateTime()) : "" // 日期安全格式化
    );

    // 一行代碼調(diào)用導(dǎo)出
    ExcelExportUtil.export(response, "財務(wù)收支記錄表", "財務(wù)明細(xì)大表", headers, recordList, extractors);
}

點評: 這種方案沒有引入任何第三方黑科技,可讀性極強,且沒有任何反射帶來的性能損耗,非常適合中小型項目。

方案二:擁抱阿里開源生態(tài),EasyExcel 的降維打擊

如果你的系統(tǒng)是一個復(fù)雜的后臺管理平臺,未來面臨著幾十上百個報表的導(dǎo)入導(dǎo)出,且動輒幾十萬級的數(shù)據(jù)量。別猶豫了,直接擁抱 Alibaba EasyExcel

相比于原生的 POI,EasyExcel 就是一輛造好的跑車。它基于注解驅(qū)動,徹底重寫了 POI 對 07 版 Excel 的解析引擎,極大地降低了內(nèi)存占用。

1. 定義專屬導(dǎo)出 VO

不要直接把數(shù)據(jù)庫的 Entity 拿去導(dǎo)出,專門建一個 View Object (VO),利用注解定義一切。

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;
import java.util.Date;

@Data
public class FinanceRecordExportVO {

    @ExcelProperty("記錄ID")
    @ColumnWidth(12)
    private Integer id;

    // 翻譯邏輯可以在 Service 層查詢后處理,或者實現(xiàn) EasyExcel 的 Converter
    @ExcelProperty("收支類型")
    @ColumnWidth(12)
    private String typeStr; 

    @ExcelProperty("金額")
    @ColumnWidth(15)
    private Double amount;

    @ExcelProperty("創(chuàng)建時間")
    @ColumnWidth(22)
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss") // 自帶強大的格式化注解
    private Date createTime;
}

2. 極致優(yōu)雅的 Controller

@PostMapping("/exportFinanceRecord")
public void exportFinanceRecord(HttpServletResponse response, @RequestBody FinanceQuery param) throws Exception {
    // 1. 查詢數(shù)據(jù)并轉(zhuǎn)換為 VO 集合 (推薦使用 MapStruct 等工具)
    List<FinanceRecordExportVO> voList = getAndConvertData(param);

    // 2. 設(shè)置響應(yīng)頭
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding("utf-8");
    String fileName = URLEncoder.encode("財務(wù)收支記錄", "UTF-8");
    response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

    // 3. 一行代碼完成寫入!
    EasyExcel.write(response.getOutputStream(), FinanceRecordExportVO.class)
             .sheet("數(shù)據(jù)明細(xì)")
             .doWrite(voList);
}

點評: 從列寬、時間格式化到樣式控制,一切皆可通過注解完成。對于復(fù)雜的分頁分批寫入、復(fù)雜表頭,EasyExcel 都有著不可替代的優(yōu)勢。

避坑指南:引入 EasyExcel 的依賴沖突問題

如果你在項目中原本已經(jīng)引入了 poipoi-ooxml,當(dāng)你滿心歡喜地加入 easyexcel 依賴后,運行項目時你大概率會遇到 NoSuchMethodErrorClassNotFoundException。

原因分析:
EasyExcel 的底層強依賴了特定版本的 POI。如果你原來的 pom.xml 中指定了較老的 POI 版本,就會覆蓋掉 EasyExcel 真正需要的版本,導(dǎo)致運行時崩潰。

最佳解決姿勢(直接“卸磨殺驢”):
既然 EasyExcel 已經(jīng)包含了 POI,最清爽的做法就是直接把原有獨立的 POI 依賴全部刪除或注釋掉,全權(quán)交給 EasyExcel 管理。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.4</version> </dependency>

注:即使你移除了單獨的 POI 依賴,你依然可以在項目中使用原生的 POI API(如 SXSSFWorkbook),因為依賴已經(jīng)被 EasyExcel 傳遞帶入,且版本絕對安全。

總結(jié)

  • 場景選擇: 如果只是零星幾個簡單的表格導(dǎo)出,追求代碼零外部侵入,方案一(Java 8 Function) 絕對能讓你眼前一亮。
  • 架構(gòu)演進: 如果是長期維護、報表繁多、數(shù)據(jù)量大的企業(yè)級后臺,強烈建議使用方案二(EasyExcel)。它不僅僅是一個導(dǎo)出工具,更是處理海量數(shù)據(jù)的標(biāo)準(zhǔn)組件。

希望這篇文章能幫你告別痛苦的 Excel 導(dǎo)出代碼編寫,把時間留給更有價值的業(yè)務(wù)邏輯!

以上就是SpringBoot導(dǎo)出Excel的最佳實踐的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot導(dǎo)出Excel的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 基于Java HashMap的死循環(huán)的啟示詳解

    基于Java HashMap的死循環(huán)的啟示詳解

    本篇文章是對Java HashMap的死循環(huán)進行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • MybatisPlus使用Wrapper實現(xiàn)條件查詢功能

    MybatisPlus使用Wrapper實現(xiàn)條件查詢功能

    這篇文章主要介紹了MybatisPlus使用Wrapper實現(xiàn)查詢功能,使用它可以實現(xiàn)很多復(fù)雜的查詢,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-06-06
  • Java日常練習(xí)題,每天進步一點點(4)

    Java日常練習(xí)題,每天進步一點點(4)

    下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你
    2021-07-07
  • 使用Sentinel實現(xiàn)流控和服務(wù)降級的代碼示例

    使用Sentinel實現(xiàn)流控和服務(wù)降級的代碼示例

    Sentinel是面向分布式、多語言異構(gòu)化服務(wù)架構(gòu)的流量治理組件,本文將詳細(xì)為大家介紹如何使用Sentinel實現(xiàn)流控和服務(wù)降級,文中有相關(guān)的代碼示例,需要的朋友可以參考下
    2023-05-05
  • 如何在Intellij中安裝LeetCode刷題插件方便Java刷題

    如何在Intellij中安裝LeetCode刷題插件方便Java刷題

    這篇文章主要介紹了如何在Intellij中安裝LeetCode刷題插件方便Java刷題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-08-08
  • Java 深入淺出講解代理模式

    Java 深入淺出講解代理模式

    代理模式是Java常見的設(shè)計模式之一。所謂代理模式是指客戶端并不直接調(diào)用實際的對象,而是通過調(diào)用代理,來間接的調(diào)用實際的對象
    2022-03-03
  • java中BigDecimal用法詳解

    java中BigDecimal用法詳解

    本文詳細(xì)講解了java中BigDecimal的用法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-12-12
  • 如何在Eclipse中設(shè)置Oracle的JDBC

    如何在Eclipse中設(shè)置Oracle的JDBC

    以下是對在Eclipse中設(shè)置Oracle的JDBC的具體操作方法進行了詳細(xì)的分析介紹,需要的朋友可以過來參考下
    2013-08-08
  • SWT(JFace) Menu、Bar...體驗代碼

    SWT(JFace) Menu、Bar...體驗代碼

    SWT(JFace)體驗之Menu、Bar實現(xiàn)代碼。
    2009-06-06
  • SpringBoot超詳細(xì)分析啟動流程

    SpringBoot超詳細(xì)分析啟動流程

    今天小編就為大家分享一篇關(guān)于SpringBoot整個啟動過程的分析,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2022-07-07

最新評論

泊头市| 宝兴县| 沙雅县| 吴川市| 松溪县| 商都县| 定日县| 武城县| 富宁县| 织金县| 专栏| 嘉兴市| 三台县| 峨边| 海门市| 孝昌县| 牟定县| 合川市| 思茅市| 秭归县| 内黄县| 台前县| 蒙阴县| 建宁县| 甘孜| 成都市| 阿巴嘎旗| 菏泽市| 买车| 隆化县| 华容县| 固始县| 潜山县| 曲沃县| 道孚县| 文成县| 华容县| 都兰县| 双牌县| 亚东县| 容城县|