JXL (Java Excel API) 是輕量級純 Java 讀寫 Excel (.xls) 庫,雖已停止維護(不支援 .xlsx),但在舊系統維護、簡單報表匯出仍廣泛使用。本文提供完整可運行範例,涵蓋資料庫查詢、樣式格式化、日期/數字格式、異常處理與資源釋放。
<dependency>
<groupId>net.sourceforge.jexcelapi</groupId>
<artifactId>jxl</artifactId>
<version>2.6.12</version>
</dependency>
import jxl.*;
import jxl.write.*;
import jxl.format.*;
import java.io.File;
import java.sql.*;
import java.util.Date;
public class ExcelReportGenerator {
public void generateReport(String jdbcUrl, String user, String pass,
String sql, String outputPath) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
WritableWorkbook workbook = null;
try {
// 1. 資料庫連線
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(jdbcUrl, user, pass);
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
// 2. 建立 Excel 工作簿
File file = new File(outputPath);
workbook = Workbook.createWorkbook(file);
WritableSheet sheet = workbook.createSheet("報表數據", 0);
// 3. 定義樣式
WritableFont headerFont = new WritableFont(WritableFont.ARIAL, 12, WritableFont.BOLD, false,
UnderlineStyle.NO_UNDERLINE, Colour.BLUE);
WritableCellFormat headerFormat = new WritableCellFormat(headerFont);
headerFormat.setAlignment(Alignment.CENTRE);
headerFormat.setBackground(Colour.GRAY_25);
WritableFont titleFont = new WritableFont(WritableFont.ARIAL, 10, WritableFont.BOLD, false,
UnderlineStyle.NO_UNDERLINE, Colour.RED);
WritableCellFormat titleFormat = new WritableCellFormat(titleFont);
WritableFont dataFont = new WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false,
UnderlineStyle.NO_UNDERLINE, Colour.BLACK);
WritableCellFormat dataFormat = new WritableCellFormat(dataFont);
dataFormat.setBorder(Border.ALL, BorderLineStyle.THIN);
// 數字格式:保留 5 位小數
NumberFormat numFmt = new NumberFormat("0.00000");
WritableCellFormat numberFormat = new WritableCellFormat(dataFont, numFmt);
numberFormat.setBorder(Border.ALL, BorderLineStyle.THIN);
// 日期格式
DateFormat dateFmt = new DateFormat("yyyy-MM-dd");
WritableCellFormat dateFormat = new WritableCellFormat(dataFont, dateFmt);
dateFormat.setBorder(Border.ALL, BorderLineStyle.THIN);
// 4. 寫入標題列
ResultSetMetaData meta = rs.getMetaData();
int colCount = meta.getColumnCount();
for (int i = 1; i <= colCount; i++) {
Label label = new Label(i - 1, 0, meta.getColumnName(i), headerFormat);
sheet.addCell(label);
}
// 5. 寫入數據列
int row = 1;
while (rs.next()) {
for (int i = 1; i <= colCount; i++) {
int colType = meta.getColumnType(i);
Object value = rs.getObject(i);
WritableCell cell;
if (value == null) {
cell = new Label(i - 1, row, "", dataFormat);
} else if (colType == Types.INTEGER || colType == Types.BIGINT ||
colType == Types.SMALLINT || colType == Types.TINYINT) {
cell = new Number(i - 1, row, rs.getLong(i), dataFormat);
} else if (colType == Types.DECIMAL || colType == Types.NUMERIC ||
colType == Types.REAL || colType == Types.FLOAT ||
colType == Types.DOUBLE) {
cell = new Number(i - 1, row, rs.getDouble(i), numberFormat);
} else if (colType == Types.DATE || colType == Types.TIMESTAMP) {
cell = new DateTime(i - 1, row, rs.getTimestamp(i), dateFormat);
} else {
cell = new Label(i - 1, row, value.toString(), dataFormat);
}
sheet.addCell(cell);
}
row++;
}
// 6. 自動調整欄寬 (可選)
for (int i = 0; i < colCount; i++) {
sheet.setColumnView(i, 20);
}
workbook.write();
System.out.println("Excel 報表產生成功:" + outputPath);
} catch (Exception e) {
throw new RuntimeException("產生 Excel 報表失敗: " + e.getMessage(), e);
} finally {
// 7. 關鍵:正確關閉資源順序
try { if (workbook != null) workbook.close(); } catch (Exception ignore) {}
try { if (rs != null) rs.close(); } catch (Exception ignore) {}
try { if (pstmt != null) pstmt.close(); } catch (Exception ignore) {}
try { if (conn != null) conn.close(); } catch (Exception ignore) {}
}
}
public static void main(String[] args) {
new ExcelReportGenerator().generateReport(
"jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=UTF-8",
"root", "password",
"SELECT id, name, price, create_time FROM products",
"D:/reports/products.xls"
);
}
}
WritableCellFormat 為不可變物件,**同一格式全表共用單一實例**,避免建立大量重複格式物件導致記憶體暴增。NumberFormat / DateFormat 傳入 Java 標準格式字串(0.00000、yyyy-MM-dd)。WritableCellFormat(font, format)。透過 ResultSetMetaData.getColumnType() 判斷 JDBC 類型,映射至 JXL Label/Number/DateTime,確保 Excel 儲存格類型正確(而非全存為文字)。
| 問題 | 原因 | 解決 |
|---|---|---|
| 中文亂碼 | JVM 預設編碼非 UTF-8 | JDBC URL 加 characterEncoding=UTF-8;JXL 內部用 UTF-16 無需額外設定 |
| 日期顯示為數字 | 未套用 DateFormat | 建立 DateTime 時傳入 WritableCellFormat(dateFont, dateFormat) |
| 數字科學記號 | 預設格式 | 套用 NumberFormat("0.00") 等固定小數格式 |
| 欄寬過窄 | 預設寬度 | sheet.setColumnView(col, width) 或計算內容長度動態設定 |
| 資源洩漏 | 未正確 close Workbook/Connection | finally 區塊分別 try-catch 關閉,順序:Workbook → ResultSet → Statement → Connection |
Workbook→Workbook、Sheet→Sheet、Label→Cell.setCellValue(String)、Number→Cell.setCellValue(double)。JXL 以簡潔 API 實現 Excel .xls 產出,核心在於樣式物件複用、類型正確映射、資源安全釋放三大原則。雖屬經典庫,上述最佳實踐在遷移至 POI 或其他報表工具時同樣適用,建議開發者內化為報表開發肌肉記憶。