核心模組:
ResultSet 轉為 DBList(欄位名 + 資料矩陣)public class DBList {
private List rowTitle; // 欄位名
private List> valueList; // 資料列
private int rowCount, colCount;
// getter/setter 省略
}
public static void exportToExcel(HttpServletRequest req, HttpServletResponse resp, DBQueryData q) {
DBList data = new DBHelper().search(q);
Workbook wb = createWorkbook(data, q.getTableName());
String fileName = q.getTableName() + ".xls";
// 瀏覽器編碼相容
String ua = req.getHeader("User-Agent");
boolean isChrome = ua != null && ua.contains("Chrome");
try {
fileName = isChrome ? URLEncoder.encode(fileName, "UTF-8")
: new String(fileName.getBytes("GB2312"), "ISO8859-1");
} catch (UnsupportedEncodingException ignore) {}
resp.setHeader("Content-Disposition", "attachment; filename=" + fileName);
resp.setContentType("application/vnd.ms-excel");
wb.write(resp.getOutputStream());
}
private static Workbook createWorkbook(DBList data, String name) {
Workbook wb = new HSSFWorkbook(); // .xls 格式,若需 .xlsx 改 XSSFWorkbook
int totalRows = data.getRowCount();
if (totalRows > 65535) {
int sheets = (totalRows + 65534) / 65535;
for (int i = 0; i < sheets; i++) {
Sheet sheet = wb.createSheet(name + (i + 1));
writeSheet(sheet, data, i * 65535, Math.min((i + 1) * 65535, totalRows));
}
} else {
Sheet sheet = wb.createSheet(name);
writeSheet(sheet, data, 0, totalRows);
}
return wb;
}
CellType 轉換:字串、數值(含日期/科學記號)、布林、空值List>
交給 DBHelper.SQLInsert() 批次入庫// 導出關鍵:寫入 UTF-8 BOM (EF BB BF)
outputStream.write(new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF});
outputStream.write(csvContent.getBytes("UTF-8"));
// 導入:BufferedReader 逐行讀取,split(",") 簡單解析
// 注意:正式專案建議使用 Apache Commons CSV 或 OpenCSV 處理引號、逗號轉義
@PostMapping("/upload")
public Map> upload(HttpServletRequest req, DBQueryData q) throws IOException {
MultipartFile file = ((MultipartHttpServletRequest) req).getFile("file");
String ext = FilenameUtils.getExtension(file.getOriginalFilename()).toLowerCase();
InputStream is = file.getInputStream();
if ("xls".equals(ext) || "xlsx".equals(ext)) {
return ReadExcel.readExcel(is, file.getOriginalFilename(), q);
} else if ("csv".equals(ext)) {
return CSVUtils.importCsv(is, q);
}
throw new IllegalArgumentException("不支援的檔案類型");
}
@GetMapping("/export/excel")
public void exportExcel(HttpServletRequest req, HttpServletResponse resp, DBQueryData q) {
exportToExcel(req, resp, q);
}
@GetMapping("/export/csv")
public void exportCsv(HttpServletResponse resp, DBQueryData q) throws Exception {
CSVUtils.exportCsv(resp, q);
}
| 坑點 | 解決方案 |
|---|---|
| 大量資料導出 OOM | 使用 SXSSFWorkbook (POI 流式寫入) 或分批查詢 + 分 Sheet |
| 日期/數值格式錯誤 | 統一用 DataFormatter (POI 內建) 取得顯示文字,避免手動判斷 CellStyle |
| CSV 包含逗號/換行/引號 | 改用 CSVParser (Commons CSV) 正確解析 RFC 4180 |
| 中文檔名下載亂碼 | RFC 5987 編碼:filename*=UTF-8''%E6%96%87%E4%BB%B6.csv |
| 批次插入效能 | PreparedStatement.addBatch() + executeBatch(),配合 rewriteBatchedStatements=true (MySQL) |
ConnectionManager 單例,改用 DataSource (HikariCP) + Spring 事務管理FileParser 介面,支援策略模式擴充新格式 (JSON, XML, Parquet)JDBC + POI + CSV 組合構建了資料庫與試算表雙向互通的基礎通道。掌握流式處理、編碼相容、批次寫入優化,即可勝任絕大多數企業級匯入匯出需求。