前后端实现 excel 导入功能
前言
在Java领域解析中、生成excel 比较有名的框架有Apache poi、jxl等。但他们都有一个缺点就是非常的耗内存。如果说系统的并发不高还行,若是并发来了后一定会内存溢出或者JVM频繁的垃圾回收。 EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能很大的减少占用内存的主要原因是在解析文件时没有将数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。 EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。
1、前端实现
<a-upload
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
:customRequest="customRequest"
@change="customChange"
:disabled="uploadDisabled">
<a-button type="primary"
:icon="uploadIcon" :disabled="uploadDisabled">导入</a-button>
</a-upload>
这段代码中 a-upload 标签表示是在页面中引入了导入组件`
`accept 中这段代码表示导入的文件只能是 excel 导入[.xlsx 或 .xls 都支持]`
`customRequest 表示自定义方法代替默认方法去实现文件导入/上传操作`
`@change 事件是在文件上传中、完成、失败都会调用这个函数`
`disabled 是否禁用上传组件`
`a-button 创建一个操作按钮,type 为 primary 表示这是一个主要按钮,icon 表示为按钮设置一个图标`
`disabled是否禁用此按钮
2、属性定义
将上面自定义的属性在data中定义好
to-top 的样式为这个:这个样式也是用的 Ant Design Vue 中的 icon图标库

export default {
data() {
return {
// 导入excel时的icon图标
uploadIcon: 'to-top',
/
/ 导入excel时是否禁用上传按钮
uploadDisabled: false
}
}
}
3、方法实现
/**
* 自定义导入文件方法
* @param data 上传的 excel 文件
*/
customRequest(data) {
const file = data.file; // 需要上传的 excel 文件
const formData = new FormData();
formData.append('file', file);
data.onProgress();
batchImportGetRecord(formData).then(res => {
if (res) {
this.$message.success('导入成功');
}
else {
this.$message.error('导入失败');
}
}).finally(() => {
this.switchIconAndStatus(false);
});
}
上传中、完成、失败都会调用这个函数`
`data.file.status 是文件上传的一些状态

/**
* 导入功能的 change 事件
* @param data 上传文件过程中的文件状态信息
*/
customChange(data) {
if (data.file.status === 'uploading') {
this.switchIconAndStatus(true);
}
else if
(data.file.status === 'done') {
this.switchIconAndStatus(false);
}
else if (data.file.status === 'error') {
this.switchIconAndStatus(false);
}
}
/**
* 导入按钮的图片和状态切换
* @param flag 根据此标识来区分正在导入还是导入成功或未导入的图标及禁用情况
*/
switchIconAndStatus(flag) {
if (flag) { this.uploadIcon = 'loading'; this.uploadDisabled = true; }
else {
this.uploadIcon = 'to-top';
this.uploadDisabled = false;
} }
4、后端实现
4.1、控制层 Controller 实现
后台接口,图片上传请求默认为 post 请求,通过 MultipartFile 类型接收上传的文件,注意这里的 @RequestParam("file")
括号中的参数要和前端上传的参数名称一致,不一致后台就接收不到这个参数
@PostMapping(value = "import")
public Boolean importExcelData(@RequestParam("file") MultipartFile file)
{
return studentService.importExcelData(file);
}
4.2、务层 Service 实现
这里举例导入一个学生信息表 excel 文件

@Override
@Transactional(rollbackFor = Exception.class)
public boolean importExcelData(MultipartFile file) {
List<Student> list = new ArrayList<Student>();
Student student = null;
try {
// 通过文件输入流读取到对应的 workbook 工作簿
XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream());
// 只解析第一张 sheet 工作表
XSSFSheet sheet = workbook.getSheetAt(0);
// 遍历第一个工作表的所有行
for (int i = 0; i < sheet.getPhysicalNumberOfRows(); i++) {
if (i == 0) continue;
// 跳过标题行
XSSFRow row = sheet.getRow(i);
// 获取工作表中的某一行,通过下标获取
if (row == null) continue;
// 跳过空行
// 构造要批量插入的Student对象
student = new Student();
// 遍历一个行中的所有列
for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) {
XSSFCell cell = row.getCell(j);
// 获取一行中的某个单元格,通过下标获取
if (cell == null) continue;
// 跳过空单元格
// 获取单元格中的字符串内容
String cellValue = cell.getStringCellValue();
// 获取单元格中的数字内容
double cellValue2 = cell.getNumericCellValue();
// 判断单元格是第几个,从零开始
switch (j) {
case 0: // 第一列就是姓名
student.setName(cellValue);
// 给实体类的setter属性赋值
break;
case 1:
// 年龄
student.setAge(cellValue2);
break;
case 2:
// 爱好
student.setHobby(cellValue);
break;
case 3:
// 家庭地址
student.setAddress(cellValue);
break;
case 4:
// 出生日期
// 如果没有特意去定义 excel 中的日期,那么获取到的日期就是字符串类型
// 这里将字符串日期转换为日期格式 LocalDateTime 或 Date
// 1. 将日期转换为 LocalDateTime
// LocalDateTime time = LocalDateTime.parse(cellValue, DateTimeFormatter.ofPattern("yyyy年M月d日HH:mm:ss")); // 2. 将日期转换为 Date
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年M月d日HH:mm:ss"); Date date = sdf.parse(cellValue); student.setBirthday(date);
break;
}
}
list.add(student);
}
// 做一下批量添加学生信息的操作即可,这里使用 MyBatis-Plus 提供的方法进行批量新增
studentService.saveBatch(list);
return true; }
catch (Exception e) {
e.printStackTrace();
System.err.println("导入失败"); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// 手动回滚代码
return false;
}
}
代码解读:
通过
file.getInputStream()
构建一个 workbookwork.getSheetAt(index)
通过此方法获取工作表,通过索引来获取,索引从零开始sheet.getPhysicalNumberOfRows()
方法可以获取 sheet 工作表中的所有行的数量sheet.getRow(index)
通过下标获取对应的行,下标都是从零开始row.getPhysicalNumberOfCells()
方法可以获取到一行中所有单元格的数量row.getCell(index)
通过下标获取对应的单元格,下标都是从零开始cell.getStringCellValue()
此方法用于获取单元格中为字符串类型的内容值,相关的方法有多种,可以获取 Boolean,日期类型,数字类型等….

最后经过读取所有内容后将单元格内容一行行的读取设置到 实体类中,并将实体类经过每次循环都添加到 list 集合中,最后通过批量插入表的操作给插入到数据库中,比起来一条条的插入,批量插入明显效率更高,因为后台请求数据库也是在消耗网络资源,中间一去一回也是浪费时间,而批量插入明显网络资源消耗的更少。
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
添加事务后,当导入失败时,可以进行代码回滚操作`
4.3、测试

导入成功!