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

SpringBoot導出Excel的最佳實踐

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

引言:為什么我們需要封裝 Excel 導出?

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

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

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

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

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

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

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

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

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

核心工具類代碼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 導出工具類
 */
public class ExcelExportUtil {
    /**
     * 核心導出方法
     * @param response   HttpServletResponse
     * @param fileName   文件名
     * @param title      表格大標題(傳空則不生成)
     * @param headers    表頭集合
     * @param dataList   數據集合 List<T>
     * @param extractors 數據提取規(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("表頭列數與數據提取規(guī)則數量不一致!");
        }
        // 使用 SXSSFWorkbook 防 OOM,內存僅保留 100 行
        SXSSFWorkbook workbook = new SXSSFWorkbook(100);
        Sheet sheet = workbook.createSheet("Sheet1");
        int rowIndex = 0;
        // 1. 生成大標題 (可選)
        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. 填充數據(核心亮點:使用 Function 動態(tài)取值并轉換)
        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. 寫出響應流
        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)調用:爽到飛起的 Controller

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

@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)建時間");

    // 定義數據提取與轉換規(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()) : "" // 日期安全格式化
    );

    // 一行代碼調用導出
    ExcelExportUtil.export(response, "財務收支記錄表", "財務明細大表", headers, recordList, extractors);
}

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

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

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

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

1. 定義專屬導出 VO

不要直接把數據庫的 Entity 拿去導出,專門建一個 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 層查詢后處理,或者實現 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. 查詢數據并轉換為 VO 集合 (推薦使用 MapStruct 等工具)
    List<FinanceRecordExportVO> voList = getAndConvertData(param);

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

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

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

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

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

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

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

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

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

總結

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

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

以上就是SpringBoot導出Excel的最佳實踐的詳細內容,更多關于SpringBoot導出Excel的資料請關注腳本之家其它相關文章!

相關文章

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

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

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

    MybatisPlus使用Wrapper實現條件查詢功能

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

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

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

    使用Sentinel實現流控和服務降級的代碼示例

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

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

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

    Java 深入淺出講解代理模式

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

    java中BigDecimal用法詳解

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

    如何在Eclipse中設置Oracle的JDBC

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

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

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

    SpringBoot超詳細分析啟動流程

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

最新評論

大新县| 若尔盖县| 安乡县| 上栗县| 甘南县| 新昌县| 额敏县| 明溪县| 东乌珠穆沁旗| 绥中县| 长垣县| 宝兴县| 故城县| 蒙城县| 涪陵区| 承德市| 剑河县| 司法| 温泉县| 兰西县| 蓝山县| 岑巩县| 从化市| 酒泉市| 台中县| 江安县| 定南县| 浑源县| 镇坪县| 武山县| 神农架林区| 阿尔山市| 安乡县| 新田县| 波密县| 吉首市| 海门市| 岑溪市| 邵武市| 德庆县| 巨野县|