在日常的企业系统或后台管理系统中,数据的 Excel 导入导出是非常常见的需求。传统方式通常是:
- 每张表都写一个专门的导入导出方法;
- 每张表都建立一个 Java Bean 类,硬编码字段;
- 新增或修改表结构时需要修改大量代码。
这些方式带来的问题有:代码重复多、维护成本高、灵活性差。
因此,本文基于 Spring Boot 3.3 + EasyExcel 实现一个 "支持任意表结构、无需绑定实体类、异步处理大文件导入" 的通用 Excel 导入导出功能。
技术选型与优势
技术 | 用途 |
---|---|
Spring Boot 3.3 | 构建 RESTful Web 项目 |
EasyExcel | 快速读取/写入 Excel 文件 |
JdbcTemplate | 动态操作任意表结构 |
ThreadPool | 支持异步导入,释放主线程 |
数据库准备(支持任意表结构)
-- 用户表
CREATE TABLE user(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
phone VARCHAR(50),
id_card VARCHAR(50),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 商品表
CREATETABLE product (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
price DECIMAL(10,2),
stock INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
通用导入导出接口设计
@RestController
@RequestMapping("/excel")
@RequiredArgsConstructor
public class ExcelController {
private final JdbcTemplate jdbcTemplate;
private final ThreadPoolTaskExecutor taskExecutor;
/**
* Excel 导入任意表(异步)
*/
@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();
// 获取目标表字段名(排除主键)
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列数与表字段不匹配");
}
// 异步处理
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("文件上传成功,已异步导入中");
}
/**
* Excel 导出任意表
*/
@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("数据")
.doWrite(excelData);
}
}
线程池配置支持异步导入
@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">导入 Excel</button>
</form>
<a href="/excel/export?tableName=user">导出用户表</a>
总结:如何提升系统通用能力
通过本文的设计与实战,我们实现了一个通用 Excel 导入导出框架,具备如下优势:
- ✅ 高通用性:支持任意数据库表结构导入导出
- ✅ 低维护成本:无需重复写实体类和 mapper
- ✅ 异步处理能力:导入可处理大文件不阻塞主线程
- ✅ 适配前后端分离/低代码平台使用场景
如果你正在开发一个后台系统、BI平台或需要支持可配置表单数据导入导出功能的系统,这种通用设计无疑能大大提升系统的灵活性和扩展性。