1. Overview of EasyExcel
1.1 Basic functions of EasyExcel
-
Data import: reduce input workload
-
Data Export: Statistics Archive
-
Data transmission: data transmission between heterogeneous systems
1.2 Problems with other parsing frameworks
Java
Domain analysis and generation Excel
of well-known frameworks include Apache poi
, jxl
etc.
But they all have a serious problem that consumes a lot of memory.
If the system has a small amount of concurrency, it may be okay, but once the concurrency is up, it will definitely appear OOM
or JVM
be frequent full gc
.
1.3 Advantages of EasyExcel
EasyExcel
exce
It is an l processing framework open sourced by Alibaba . It is famous for its simple use and memory saving .
EasyExcel
The main reason for greatly reducing the memory usage is that Excel
the file data is not loaded into the memory all at once during parsing.
Instead, it reads data line by line from the disk and parses them one by one.
EasyExcel
Adopt a line-by-line parsing mode, and notify the processing ( AnalysisEventListener
) of the line-by-line parsing result in the observer mode.
In summary:
EasyExcel
is an open source project based on Java
simple and memory-efficient read and write .Excel
M
It supports reading and writing hundreds of files while saving memory as much as possible Excel
.
1.4 Official document address
https://alibaba-easyexcel.github.io/index.html
1.5 github address
https://github.com/alibaba/easyexcel
2. Introducing dependencies
<dependencies>
<!-- 查看excel的maven仓库 https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.2</version>
</dependency>
</dependencies>
3. Write operation of EasyExcel
3.1 Create entity classes
// lombok注解,自动生成getter/setter、构造方法、toString等实体类通用方法
@Data
public class Student {
// @ExcelProperty注解 设置当前字段表头名称和所在的列的索引,index为0,代表sno字段会被插入到第一列
@ExcelProperty(value = "学生编号",index = 0)
private int sno;
@ExcelProperty(value = "学生姓名",index = 1)
private String sname;
// @ExcelIgnore 导出时忽略这个字段
@ExcelIgnore
private String ignore;
}
3.2 Writing methods to generate data
//循环设置要添加的数据,最终封装到list集合中
private static List<Student> data() {
List<Student> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Student data = new Student();
data.setSno(i);
data.setSname("张三"+i);
list.add(data);
}
return list;
}
3.3 Write data to a single sheet file in Excel
Writing method 1 (common api):
public static void main(String[] args) throws Exception {
// 指定文件位置
String fileName = "D:\\test.xlsx";
// 指定各个参数并向指定位置写出一个Excel文件
EasyExcel.write(fileName, Student.class).sheet(0, "学生列表").doWrite(data());
}
Writing method 2 (lambda expression):
public static void main(String[] args) throws Exception {
// 指定文件位置
String fileName = "D:\\test.xlsx";
EasyExcel.write(fileName, Student.class).sheet("学生列表")
.doWrite(() -> {
// 这个函数中返回需要的数据即可
return data();
});
}
Detailed explanation of the parameters passed:
-
write
The method needs to specify the location of the file and which class to use for writing . The default location for writing data is the firstsheet
-
sheet
The method needs to specify the name of the one currently writing datasheet
(required) and the numbersheet
to write data to (not required) -
doWrite
The method can receive a collection of data to be written or pass in anlambda
expression, and the return value of the expression is still a collection of data
3.4 Write data to multiple sheet files in Excel
public static void main(String[] args) throws Exception {
// 指定文件位置
String fileName = "D:\\test.xlsx";
// 创建Excel写出对象
ExcelWriter excelWriter = EasyExcel.write(fileName, Student.class).build();
// 创建写出的Excel文件的sheet对象 并指定sheet的位置和名字
WriteSheet sheet1 = EasyExcel.writerSheet(0, "学生列表1").build();
WriteSheet sheet2 = EasyExcel.writerSheet(1, "学生列表2").build();
// 向sheet中写入数据
excelWriter.write(data(), sheet1);
excelWriter.write(data(), sheet2);
//关闭流对象(这是和上面写法的区别,上面的写法会自动关,这里需要手动关)
excelWriter.finish();
}
4. Write operation of EasyExcel in web application (download)
In web
the application, the operation of export (download) is often encountered Excel
.
Mainly EasyExcel
use HttpServletResponse
the object to get the output stream to write out the file to the browser.
import org.springframework.web.multipart.MultipartFile;
/**
* 在web应用中读写案例
**/
@Controller
public class WebTest {
@Autowired
private UploadDAO uploadDAO;
/**
* 文件下载(失败了会返回一个有部分数据的Excel)
* 1. 创建excel对应的实体对象
* 2. 设置返回的 参数
* 3. 直接写出Excel
* 这里注意,doWrite执行完后会自动关闭流, 主动finish也会自动关闭OutputStream
*/
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
// 这里注意 使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
// 设置文件名称
response.setHeader("Content-disposition",
"attachment;filename*=utf-8''" + fileName + ".xlsx");
// 写出数据
EasyExcel.write(response.getOutputStream(), Student.class)
.sheet("sheet的名称").doWrite(data());
}
/**
* 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel)
* @since 2.1.1
*/
@GetMapping("downloadFailedUsingJson")
public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
try {
response.setContentType(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// URLEncoder.encode可以防止中文乱码
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition",
"attachment;filename*=utf-8''" + fileName + ".xlsx");
// 这里需要设置不关闭流 autoCloseStream(Boolean.FALSE)
EasyExcel.write(response.getOutputStream(), Student.class)
.autoCloseStream(Boolean.FALSE)
.sheet("模板")
.doWrite(data());
} catch (Exception e) {
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Map<String, String> map = MapUtils.newHashMap();
map.put("status", "failure");
map.put("message", "下载文件失败" + e.getMessage());
response.getWriter().println(JSON.toJSONString(map));
}
}
//循环设置要添加的数据,最终封装到list集合中
private static List<Student> data() {
List<Student> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Student data = new Student();
data.setSno(i);
data.setSname("张三"+i);
list.add(data);
}
return list;
}
}
Five, Excel read operation
5.1 Create entity class
// lombok注解,自动生成getter/setter、构造方法、toString等实体类通用方法
@Data
public class Student {
// @ExcelProperty注解 设置当前字段表头名称和所在的列的索引,index为0,代表sno字段会被插入到第一列
@ExcelProperty(value = "学生编号",index = 0)
private int sno;
@ExcelProperty(value = "学生姓名",index = 1)
private String sname;
// @ExcelIgnore 导出时忽略这个字段
@ExcelIgnore
private String ignore;
}
5.2 Create a listener for read operations
1) Basic version
public class ExcelListener extends AnalysisEventListener<Student> {
//创建list集合封装最终的数据
List<Student> list = new ArrayList<>();
//读取excel表头信息时执行
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头信息:"+headMap);
}
// 读取excel内容信息时执行
// EasyExcel会会一行一行去读取excle内容,每解析excel文件中的一行数据,都会调用一次invoke方法
@Override
public void invoke(Student stu, AnalysisContext analysisContext) {
System.out.println("***" + stu);
list.add(user);
}
//读取完成后执行
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
Notice:
Custom listener classes cannot be spring
managed, requiring a new object excel
for each read.new
If spring
the managed object is used in it, you can use the constructor to pass it in:
// 如果用到了就在监听器中加上类似代码,去使用dao层或者service层中的逻辑把读取到的数据写到数据库中
private DemoDAO demoDAO;
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
2) When you need to use the logic of saving data in spring
@Slf4j
public class ExcelListener extends AnalysisEventListener<Student> {
//创建list集合封装从Excel文件中读取的数据
List<Student> list = new ArrayList<>();
// list中每达到10条数据就存储数据库,然后清理list ,方便内存回收
// 实际使用中可以根据服务器性能设置更多条
private static final int BATCH_COUNT = 10;
// 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。
// 如果不用存储从Excel文件中读取的数据,那么这个对象就没用
private DemoDAO demoDAO;
// 无参构造
public DemoDataListener() {
}
// 有参构造 可以在每次创建Listener对象的时候需要把spring管理的类传进来
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
//读取excel表头信息时执行
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头信息:"+headMap);
}
// 读取excel内容信息时执行
// EasyExcel会会一行一行去读取excle内容,每解析excel文件中的一行数据,都会调用一次invoke方法
@Override
public void invoke(Student stu, AnalysisContext analysisContext) {
System.out.println("***" + stu);
list.add(user);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list = new ArrayList<Student>(BATCH_COUNT);
}
}
//读取完成后执行
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
list = new ArrayList<Student>(BATCH_COUNT);
}
// 把数据存储到数据库中
private void saveData() {
log.info(BATCH_COUNT + "条数据,开始存储数据库!", cachedDataList.size());
demoDAO.save(list);
log.info("存储数据库成功!");
}
}
5.3 Read the data of a single sheet in the Excel file
public class EasyExcelReadDemo {
public static void main(String[] args) {
// 指定要读取文件的位置
String fileName = "D:\\test.xlsx";
// read方法指定文件名名称、使用哪个实体类解析、使用哪个监听器类处理
// sheet方法指定读取哪个sheet的数据
// doRead() 方法发起最终的读取操作
EasyExcel.read(fileName, Student.class, new StudentListener()).sheet(0).doRead();
}
}
5.4 Read the data of multiple sheets of the excel file
public class EasyExcelReadDemo {
public static void main(String[] args) {
// 指定要读取文件的位置
String fileName = "D:\\test.xlsx";
// 创建Excel读对象,需要指定读取哪个Excel
ExcelReader excelReader = EasyExcel.read(fileName).build();
// 创建需要读取的Excel中的sheet对象
ReadSheet sheet1 = EasyExcel.readSheet(0)
.head(Student.class)
.registerReadListener(new StudentListener()).build();
ReadSheet sheet2 = EasyExcel.readSheet(1)
.head(Student.class)
.registerReadListener(new StudentListener()).build();
// 批量读取sheet1对象和sheet2对象中的数据
excelReader.read(sheet1, sheet2);
// 关闭流资源
excelReader.finish();
}
}
6. Read operation (upload) of EasyExcel in web application
import org.springframework.web.multipart.MultipartFile;
/**
* 在web应用中读写案例
**/
@Controller
public class WebTest {
@Autowired
private DemoDAO demoDAO;
0
/**
* 文件上传
* 1. 创建excel对应的实体对象 参照{@link UploadData}
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器
* 3. 直接读即可
*/
@PostMapping("upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(),
Student.class,
new UploadDataListener(demoDAO))
.sheet()
.doRead();
return "success";
}
//循环设置要添加的数据,最终封装到list集合中
private static List<Student> data() {
List<Student> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Student data = new Student();
data.setSno(i);
data.setSname("张三"+i);
list.add(data);
}
return list;
}
}