首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

基於 JXL 的 Excel 導出工具封裝:支持 JDBC 與內存數據雙模式

     編輯:Java 相關知識

Apache POI 雖功能強大但體積龐大、API 複雜。對於純導出 .xls (Excel 97-2003) 場景,JXL (Java Excel API) 以輕量級(<500KB)、API 簡潔、內存佔用低著稱。本文展示一個生產就緒的封裝工具類,支持 JDBC ResultSet 直接導出與內存對象列表導出雙模式,內置列寬自適應、標題顯示控制、日期/數值格式化等實用功能。

核心依賴

<dependency>
    <groupId>net.sourceforge.jexcelapi</groupId>
    <artifactId>jxl</artifactId>
    <version>2.6.12</version>
</dependency>

工具類完整實現

package com.example.excel;

import jxl.Workbook;
import jxl.write.*;
import jxl.format.Alignment;
import jxl.format.Colour;
import jxl.format.VerticalAlignment;

import java.io.File;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

public final class JxlExportUtils {

    private JxlExportUtils() {}

    /** 
     * 通用導出入口:接受數據提供者介面,統一處理表頭、樣式、寫入
     */
    public static void export(File file, DataProvider provider, 
                              String[] headers, boolean[] showHeader, int[] colWidths) 
            throws IOException, WriteException {
        WritableWorkbook workbook = null;
        try {
            workbook = Workbook.createWorkbook(file);
            WritableSheet sheet = workbook.createSheet("Sheet1", 0);
            
            // 1. 寫入表頭
            WritableFont headerFont = new WritableFont(WritableFont.ARIAL, 10, WritableFont.BOLD);
            WritableCellFormat headerFormat = new WritableCellFormat(headerFont);
            headerFormat.setAlignment(Alignment.CENTRE);
            headerFormat.setVerticalAlignment(VerticalAlignment.CENTRE);
            headerFormat.setBackground(Colour.GRAY_25);
            headerFormat.setBorder(Border.ALL, BorderLineStyle.THIN);
            
            for (int i = 0; i < headers.length; i++) {
                if (showHeader == null || showHeader[i]) {
                    sheet.addCell(new Label(i, 0, headers[i], headerFormat));
                }
                // 設置列寬 (JXL 寬度單位:1/256 字符寬度)
                if (colWidths != null && i < colWidths.length && colWidths[i] > 0) {
                    sheet.setColumnView(i, colWidths[i]);
                }
            }
            
            // 2. 寫入數據行
            WritableCellFormat dataFormat = new WritableCellFormat();
            dataFormat.setAlignment(Alignment.LEFT);
            dataFormat.setVerticalAlignment(VerticalAlignment.CENTRE);
            dataFormat.setBorder(Border.ALL, BorderLineStyle.THIN);
            dataFormat.setWrap(true); // 自動換行
            
            // 日期格式
            WritableCellFormat dateFormat = new WritableCellFormat(dataFormat);
            dateFormat.setFormat(new jxl.write.DateFormat("yyyy-MM-dd HH:mm:ss"));
            
            int row = 1;
            while (provider.hasNext()) {
                Object[] rowData = provider.next();
                for (int col = 0; col < rowData.length; col++) {
                    Object val = rowData[col];
                    if (val == null) {
                        sheet.addCell(new Label(col, row, "", dataFormat));
                    } else if (val instanceof Date) {
                        sheet.addCell(new jxl.write.DateTime(col, row, (Date) val, dateFormat));
                    } else if (val instanceof Number) {
                        sheet.addCell(new jxl.write.Number(col, row, ((Number) val).doubleValue(), dataFormat));
                    } else if (val instanceof Boolean) {
                        sheet.addCell(new jxl.write.Boolean(col, row, (Boolean) val, dataFormat));
                    } else {
                        sheet.addCell(new Label(col, row, val.toString(), dataFormat));
                    }
                }
                row++;
            }
        } finally {
            if (workbook != null) {
                workbook.write();
                workbook.close();
            }
        }
    }

    /** 數據提供者介面:解耦數據源 */
    public interface DataProvider {
        boolean hasNext();
        Object[] next();
        void close() throws Exception;
    }

    // ========== 靜態工廠方法:構建常用 DataProvider ==========

    /** JDBC ResultSet 模式 */
    public static DataProvider fromResultSet(ResultSet rs) {
        return new DataProvider() {
            @Override public boolean hasNext() {
                try { return rs.next(); } catch (SQLException e) { throw new RuntimeException(e); }
            }
            @Override public Object[] next() {
                try {
                    ResultSetMetaData meta = rs.getMetaData();
                    int cols = meta.getColumnCount();
                    Object[] row = new Object[cols];
                    for (int i = 1; i <= cols; i++) row[i-1] = rs.getObject(i);
                    return row;
                } catch (SQLException e) { throw new RuntimeException(e); }
            }
            @Override public void close() { try { rs.close(); } catch (SQLException ignored) {} }
        };
    }

    /** 內存 List 模式 (Record 為簡單數組封裝) */
    public static DataProvider fromRecords(List records) {
        return new DataProvider() {
            private int index = 0;
            @Override public boolean hasNext() { return index < records.size(); }
            @Override public Object[] next() { return records.get(index++).getValues(); }
            @Override public void close() {}
        };
    }

    /** 通用 List 模式 */
    public static DataProvider fromArrayList(List data) {
        return new DataProvider() {
            private int index = 0;
            @Override public boolean hasNext() { return index < data.size(); }
            @Override public Object[] next() { return data.get(index++); }
            @Override public void close() {}
        };
    }

    /** 簡單記錄封裝 */
    public static class Record {
        private final Object[] values;
        public Record(Object... values) { this.values = values; }
        public Object[] getValues() { return values; }
    }
}

使用示例

模式一:JDBC 直接導出 (零拷貝內存)

public void exportUsersToExcel(File file) throws Exception {
    String sql = "SELECT id, username, email, create_time, status FROM sys_user WHERE status=1";
    try (Connection conn = dataSource.getConnection();
         PreparedStatement ps = conn.prepareStatement(sql);
         ResultSet rs = ps.executeQuery()) {
        
        String[] headers = {"ID", "用戶名", "郵箱", "創建時間", "狀態"};
        boolean[] show = {true, true, true, true, true};
        int[] widths = {8, 20, 30, 22, 10};
        
        JxlExportUtils.export(file, JxlExportUtils.fromResultSet(rs), headers, show, widths);
    }
}

模式二:內存對象列表導出

public void exportReport(List records, File file) throws Exception {
    List data = records.stream()
        .map(r -> new JxlExportUtils.Record(
            r.getId(), r.getProduct(), r.getAmount(), r.getSaleDate(), r.getRegion()
        )).toList();
    
    String[] headers = {"訂單號", "產品", "金額", "銷售日期", "地區"};
    int[] widths = {15, 25, 12, 20, 15};
    
    JxlExportUtils.export(file, JxlExportUtils.fromRecords(data), headers, null, widths);
}

關鍵特性與避坑指南

特性實現細節注意事項
大數據量導出ResultSet 模式流式讀寫,不載入全量內存內存模式受限於 JVM Heap,超 5 萬行建議分批或用 SXSSF (POI)
列寬單位JXL: 1/256 字符寬度;POI: 1/256 * 256 = 字符數經驗值:中文字符寬度約 2 倍英文,預留 20-40
日期格式WritableCellFormat + DateFormat("yyyy-MM-dd")需與數據庫時區一致,避免時區偏移
數值精度Number 儲存 double,大金額用 BigDecimal 轉 String避免科學計數法顯示
中文文件名HttpServletResponse.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(name, "UTF-8"))瀏覽器兼容性需測試
.xlsx 支持JXL 不支持,需遷移 Apache POI (XSSF/SXSSF)新項目建議直接用 POI

總結

JXL 以極小體積、極簡 API、流式寫入低內存特性,成為 .xls 導出場景的經典選擇。封裝 DataProvider 介面實現數據源解耦,一套代碼同時服務 JDBC 與內存模式。對於現代項目需導出 .xlsx、超大數據量、複雜樣式(合併單元格、圖表、宏),請直接採用 Apache POI SXSSF (流式) 或 EasyExcel (阿里開源)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved