太強(qiáng)了!Spring Boot 3.3 一個(gè)接口就能搞定 Excel 導(dǎo)入導(dǎo)出所有表!
在日常的企業(yè)系統(tǒng)或后臺(tái)管理系統(tǒng)中,數(shù)據(jù)的 Excel 導(dǎo)入導(dǎo)出是非常常見的需求。傳統(tǒng)方式通常是:
- 每張表都寫一個(gè)專門的導(dǎo)入導(dǎo)出方法;
- 每張表都建立一個(gè) Java Bean 類,硬編碼字段;
- 新增或修改表結(jié)構(gòu)時(shí)需要修改大量代碼。
這些方式帶來的問題有:代碼重復(fù)多、維護(hù)成本高、靈活性差。
因此,本文基于 Spring Boot 3.3 + EasyExcel 實(shí)現(xiàn)一個(gè) "支持任意表結(jié)構(gòu)、無需綁定實(shí)體類、異步處理大文件導(dǎo)入" 的通用 Excel 導(dǎo)入導(dǎo)出功能。
技術(shù)選型與優(yōu)勢(shì)
技術(shù) | 用途 |
Spring Boot 3.3 | 構(gòu)建 RESTful Web 項(xiàng)目 |
EasyExcel | 快速讀取/寫入 Excel 文件 |
JdbcTemplate | 動(dòng)態(tài)操作任意表結(jié)構(gòu) |
ThreadPool | 支持異步導(dǎo)入,釋放主線程 |
數(shù)據(jù)庫準(zhǔn)備(支持任意表結(jié)構(gòu))
-- 用戶表
CREATETABLEuser(
id BIGINTPRIMARYKEYAUTO_INCREMENT,
name VARCHAR(100),
phone VARCHAR(50),
id_card VARCHAR(50),
created_at DATETIMEDEFAULTCURRENT_TIMESTAMP
);
-- 商品表
CREATETABLE product (
id BIGINTPRIMARYKEYAUTO_INCREMENT,
name VARCHAR(100),
price DECIMAL(10,2),
stock INT,
created_at DATETIMEDEFAULTCURRENT_TIMESTAMP
);
通用導(dǎo)入導(dǎo)出接口設(shè)計(jì)
@RestController
@RequestMapping("/excel")
@RequiredArgsConstructor
public class ExcelController {
private final JdbcTemplate jdbcTemplate;
private final ThreadPoolTaskExecutor taskExecutor;
/**
* Excel 導(dǎo)入任意表(異步)
*/
@PostMapping("/import")
public ResponseEntity<String> importExcel(@RequestParam("file") MultipartFile file,
@RequestParam("tableName") String tableName) throws IOException {
List<Map<Integer, String>> rowData = new ArrayList<>();
EasyExcel.read(file.getInputStream(), new AnalysisEventListener<Map<Integer, String>>() {
@Override
public void invoke(Map<Integer, String> data, AnalysisContext context) {
rowData.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {}
}).sheet().doRead();
// 獲取目標(biāo)表字段名(排除主鍵)
List<String> columns = jdbcTemplate.queryForList(
"SHOW COLUMNS FROM " + tableName + " WHERE Field != 'id'", String.class);
if (columns.size() != rowData.get(0).size()) {
return ResponseEntity.badRequest().body("Excel列數(shù)與表字段不匹配");
}
// 異步處理
taskExecutor.execute(() -> {
for (Map<Integer, String> row : rowData) {
String sql = "INSERT INTO " + tableName + " (" + String.join(",", columns) + ") VALUES (" +
String.join(",", Collections.nCopies(columns.size(), "?")) + ")";
Object[] values = columns.stream().map(col -> row.get(columns.indexOf(col))).toArray();
jdbcTemplate.update(sql, values);
}
});
return ResponseEntity.ok("文件上傳成功,已異步導(dǎo)入中");
}
/**
* Excel 導(dǎo)出任意表
*/
@GetMapping("/export")
public void exportExcel(@RequestParam("tableName") String tableName, HttpServletResponse response) throws IOException {
List<String> columnNames = jdbcTemplate.queryForList("SHOW COLUMNS FROM " + tableName, String.class);
List<Map<String, Object>> rows = jdbcTemplate.queryForList("SELECT * FROM " + tableName);
List<List<String>> excelData = new ArrayList<>();
excelData.add(columnNames);
for (Map<String, Object> row : rows) {
List<String> rowList = columnNames.stream().map(col -> {
Object value = row.get(col);
return value == null ? "" : value.toString();
}).collect(Collectors.toList());
excelData.add(rowList);
}
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode(tableName + "_export.xlsx", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
EasyExcel.write(response.getOutputStream())
.sheet("數(shù)據(jù)")
.doWrite(excelData);
}
}
線程池配置支持異步導(dǎo)入
@Configuration
public class ThreadConfig {
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
pool.setCorePoolSize(4);
pool.setMaxPoolSize(8);
pool.setQueueCapacity(100);
pool.setKeepAliveSeconds(30);
pool.setThreadNamePrefix("excel-import-");
pool.initialize();
return pool;
}
}
spring:
task:
execution:
pool:
core-size: 4
max-size: 8
queue-capacity: 100
前端 Thymeleaf 示例
<form method="post" enctype="multipart/form-data" action="/excel/import">
<input type="file" name="file">
<input type="text" name="tableName" placeholder="輸入表名">
<button type="submit">導(dǎo)入 Excel</button>
</form>
<a href="/excel/export?tableName=user">導(dǎo)出用戶表</a>
總結(jié):如何提升系統(tǒng)通用能力
通過本文的設(shè)計(jì)與實(shí)戰(zhàn),我們實(shí)現(xiàn)了一個(gè)通用 Excel 導(dǎo)入導(dǎo)出框架,具備如下優(yōu)勢(shì):
- ? 高通用性:支持任意數(shù)據(jù)庫表結(jié)構(gòu)導(dǎo)入導(dǎo)出
- ? 低維護(hù)成本:無需重復(fù)寫實(shí)體類和 mapper
- ? 異步處理能力:導(dǎo)入可處理大文件不阻塞主線程
- ? 適配前后端分離/低代碼平臺(tái)使用場(chǎng)景
如果你正在開發(fā)一個(gè)后臺(tái)系統(tǒng)、BI平臺(tái)或需要支持可配置表單數(shù)據(jù)導(dǎo)入導(dǎo)出功能的系統(tǒng),這種通用設(shè)計(jì)無疑能大大提升系統(tǒng)的靈活性和擴(kuò)展性。