记录自己使用easyexcel操作过程及碰到问题的解决方法,主要使用spring,springmvc,mybatiesplus框架,数据库及其他依赖配置请自行添加。
主要参考文档:https://www.yuque.com/easyexcel/doc/api
1.需要的依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
//easyexcel是在poi的基础上开发的
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>//写监听器时JSON类需要此依赖
<version>2.0.1</version>
</dependency>
2.实体类
我准备批量上传学生信息,创建实体类Student
package com.liao.heiban.pojo;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.sql.Date;
@Data
//@Accessors(chain = true)
@TableName("student")
public class Student implements Serializable {
private static final long serialVersionUID = -7608888444418873339L;
@TableId(type = IdType.AUTO)
@ExcelProperty(index = 0) //@ExcelProperty("id")
private Integer id ;
@ExcelProperty(index = 1)
private String grade ;//班级
@ExcelProperty(index = 2)
private String name;//姓名
@ExcelProperty(index =3)
private Integer age ;//年龄
@ExcelProperty(index = 4)
private String sex;//性别
@ExcelIgnore
@DateTimeFormat("yyyy-MM-dd")
private Date time;//入学时间
@ExcelIgnore
@TableField(exist = false)
private Integer classId;//班级id
}
注意:excel表格中对应的属性都需要添加@ExcelProperty()注解index代表属性在表格中的列数,从零开始,或者可以用表格中列的名字代替,例如id,但是建议只用一种格式,其他表格中没有的属性都加上@ExcelIgnore
测试文件的数据为
id在数据库是自增的,所以表格中的id可以不写数据或者将id这一列去掉,会自动赋值,由于excel时间格式会与Date类型不匹配,接收数据时需要要string类型接收,由于数据库和实体类已经写好,暂时就先将时间忽略,先不上传时间格式。
由于@Accessors与easyexcel冲突,使用此注解会导致读取的数据都为空,所以不要使用@Accessors注解
3.根据官方文档写出此类的监听器
package com.liao.heiban.util;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson2.JSON;
import com.liao.heiban.service.StudentListenerMapper;
import com.liao.heiban.pojo.Student;
//import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
//easyexcel监听器
public class StudentListener implements ReadListener<Student> {
//每隔100条数据存储数据库
private static final int BATCH_COUNT = 100;
//缓存数据
private List<Student> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
private StudentListenerMapper studentListenerMapperrs;
public StudentListener(){
studentListenerMapperrs = new StudentListenerMapper();
}
//如果使用了spring 创建listener是将spring管理的类传过来
public StudentListener(StudentListenerMapper studentListenerMapper){
this.studentListenerMapperrs = studentListenerMapper;
}
@Override
public void invoke(Student student, AnalysisContext analysisContext) {
log.info("解析到一条数据", JSON.toJSONString(student));
cachedDataList.add(student);
if (cachedDataList.size()>=BATCH_COUNT){
saveDate();
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
private void saveDate() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
studentListenerMapperrs.save(cachedDataList);
log.info("存储数据库成功!");
}
//数据解析完成调用此方法
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
saveDate();
log.info("所有数据解析完成!");
}
}
4.其他代码
controller层,service依赖注入
package com.liao.heiban.controller;
import com.liao.heiban.ov.HResult;
import com.liao.heiban.pojo.Student;
import com.liao.heiban.service.StudentService;
import lombok.experimental.Accessors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.List;
@RestController
@RequestMapping("/student")
@CrossOrigin
public class StudentController {
@Autowired
private StudentService studentService;
@PostMapping("putExcel")
public HResult putExcel(MultipartFile file){
//验证文件内容是否上传
// System.out.println(file.getName());
// try {
// InputStream inputStream = file.getInputStream();
// File file1 = new File("F:\\picture\\1.xlsx");
// FileOutputStream fileOutputStream = new FileOutputStream(file1);
// while (true){
// int b = inputStream.read();
// if(b==-1){
// break;
// }
// fileOutputStream.write(b);
// }
// inputStream.close();
// fileOutputStream.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
studentService.putExcel(file);
return HResult.susses();
}
}
service层代码
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.liao.heiban.mapper.StudentMapper;
import com.liao.heiban.pojo.Student;
import com.liao.heiban.util.StudentListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
@Slf4j
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Override
public void putExcel(MultipartFile file) {
//第一种方式:利用文件流在服务器临时文件夹生成文件调用完成后删除,通过文件路径及文件名得到read方法的第一个参数,建议用下面第二种方法
try {
InputStream is= file.getInputStream();
String property = System.getProperty("java.io.tmpdir");
String path = property+"xx\\";
System.out.println(path);
File file1 = new File(path);
if (!file1.exists()){
file1.mkdirs();
}
String file1Path = path+System.currentTimeMillis()+".xlsx";
System.out.println(file1Path);
File file2 = new File(file1Path);
FileOutputStream fileOutputStream = new FileOutputStream(file2);
while (true){
int b = is.read();
if(b==-1){
break;
}
fileOutputStream.write(b);
}
try {
EasyExcel.read(file1Path,Student.class,new StudentListener(studentListenerMapper)).sheet().doRead();
//官方的demo方法
// String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// EasyExcel.read(file1Path,Student.class,new PageReadListener<Student>(dataList -> {
// for (Student demoData : dataList) {
// log.info("读取到一条数据{}", JSON.toJSONString(demoData));
// System.out.println(demoData);
// }
// })).sheet().doRead();
}catch (Exception e){
e.printStackTrace();
}finally {
is.close();
fileOutputStream.close();
file2.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
// 第二种方式,read方法中把路径当做参数调用file()方法,利用文件路径及名称生成数据流,观察EasyExcel的read方法,其中第一个参数也可以是输入流,直接用传入的Multipartfile生成输入流
// try {
// EasyExcel.read(file.getInputStream(),Student.class,new StudentListener(studentListenerMapper)).sheet().doRead();
// } catch (IOException e) {
// e.printStackTrace();
// }
}
}
创建一个类调用StudentMapper进行存储数据,这个类也被监听器中的构造方法调用所调用,生成的list就是此类方法的参数。
import com.liao.heiban.mapper.StudentMapper;
import com.liao.heiban.pojo.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
;
@Component
public class StudentListenerMapper{
@Autowired
StudentMapper studentMapper;
public void save(List<Student> cachedDataList) {
for (int i = 0; i < cachedDataList.size(); i++) {
System.out.println(cachedDataList);
studentMapper.insert(cachedDataList.get(i));
}
}
}
此方法会导致每次循环都链接数据库存储,耗费时间比较长。建议使用mybaties对xml文件进行编辑,使用事务进行批处理方式或者foreach,或者ExecutorType.BATCH
mapper层代码
package com.liao.heiban.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liao.heiban.pojo.Student;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
@Mapper
public interface StudentMapper extends BaseMapper<Student> {
}
启动服务后可以在postman上模拟前端上传文件
由于我们在controller层设置返回数据,所以只能在postman上看到这个结果
但是后台我们有解析之后输出list的代码,所以能在后台看见解析后的list
可以看到两条数据已经传入,在数据库中刷新表就可以看到新增的数据了