java在项目中实现excel导入导出 java在实战项目中实现excel导入导出 --前后端分离项目

一、初识EasyExcel*

1. Apache POI

先说POI,有过报表导入导出经验的同学,应该听过或者使用。

Apache POI是Apache软件基金会的开源函式库,提供跨平台的Java API实现Microsoft Office格式档案读写。但是存在如下一些问题:

1.1 学习使用成本较高

对POI有过深入了解的才知道原来POI还有SAX模式(Dom解析模式)。但SAX模式相对比较复杂,excel有03和07两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。

想要了解清楚这两种解析方式,才去写代码测试,估计两天时间是需要的。再加上即使解析完,要转换到自己业务模型还要很多繁琐的代码。总体下来感觉至少需要三天,由于代码复杂,后续维护成本巨大。

POI的SAX模式的API可以一定程度的解决一些内存溢出的问题,但是POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大,一个3M的Excel用POI的SAX解析,依然需要100M左右内存。

1.2 POI的内存消耗较大

大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。

总体上来说,简单写法重度依赖内存,复杂写法学习成本高。

特点
  1. 功能强大

  2. 代码书写冗余繁杂

  3. 读写大文件耗费内存较大,容易OOM

2. EasyExcel

2.1 重写了POI对07版Excel的解析
  • EasyExcel重写了POI对07版Excel的解析,可以把内存消耗从100M左右降低到10M以内,并且再大的Excel不会出现内存溢出,03版仍依赖POI的SAX模式。

  • 下图为64M内存1分钟内读取75M(46W行25列)的Excel(当然还有急速模式能更快,但是内存占用会在100M多一点)

  • 在上层做了模型转换的封装,让使用者更加简单方便
特点
  1. 在数据模型层面进行了封装,使用简单
  2. 重写了07版本的Excel的解析代码,降低内存消耗,能有效避免OOM
  3. 只能操作Excel
  4. 不能读取图片

二、快速入门–QuickStart

0、导入依赖坐标


 com.alibaba
 easyexcel
 2.1.6
 org.projectlombok
 lombok
 1.16.20
 org.springframework.boot
 spring-boot-starter-test

导入easyexcel-2.1.6坐标的时候,已依赖传递导入poi-3.17的POI。

1、最简单的读

1.1、需求、准备工作
/**
 * 需求:单实体导入(从磁盘将文件导入到应用程序)
 * 导入Excel学员信息到系统。
 * 包含如下列:姓名、性别、出生日期
 * 模板详见:学员信息.xlsx
 */

学员信息.xlsx

1.2、编写导出数据的实体类
package com.taotie.test;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
// 基于lombok
@Data
@NoArgsConstructor
@AllArgsConstructor
@HeadRowHeight(20) // 指定列头行高
@ColumnWidth(20) // 指定列宽
public class Student {
 /**
 * 学生姓名
 */
 @ExcelProperty(value = "学生姓名", index = 0)
 private String name;
 /**
 * 学生性别
 */
 @ExcelProperty(value = "学生性别", index = 2)
 private String gender;
 /**
 * 学生出生日期
 */
 @ExcelProperty(value = "学生出生日期", index = 1)
 private Date birthday;
 /**
 * id
 */
 // @ExcelProperty(value = "编号",index = 3)
 @ExcelIgnore // 忽略,不读取
 private String id;
}

注解: 文章后面有详解

1.3、 读取Excel文件(上传)

调用EasyExcelAPI读取的Excel文件的测试类StudentReadDemo

package com.taotie.test;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
@SpringBootTest 
public class TestEasyExcel {
 /**
 * 测试读取数据
 */
 @Test
 public void testRead() {
 // 读取文件,读取完之后会自动关闭
 /**
 * 参数1:pathName 文件路径;"d:\\学员信息.xls"
 * 参数2:head	 每行数据对应的实体;Student.class
 * 参数3:readListener	读监听器,每读一样就会调用一次该监听器的invoke方法
 * 参数4:sheet方法参数: 工作表的顺序号(从0开始)或者工作表的名字,不传默认为0
 */
 // // 封装工作簿对象
 // ExcelReaderBuilder workBook = EasyExcel.read
 // ("E:\\学员信息.xlsx",
 // Student.class,
 // new StudentReadListener( ));
 // // 封装工作表
 // ExcelReaderSheetBuilder sheet1 = workBook.sheet( );
 // // 读取
 // sheet1.doRead( );
 
 // 最简单的写法
 EasyExcel.read("E:\\学员信息.xlsx",Student.class,new StudentReadListener()).sheet().doRead();
 }
}

读取Excel的监听器,用于处理读取产生的数据

package com.taotie.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.taotie.domain.Student;
/**
 * @Author Vsunks.v
 * @Description:
 */
public class StudentReadListener extends AnalysisEventListener {
 // 每读一行,会调用该invoke方法一次
 @Override
 public void invoke(Student data, AnalysisContext context) {
 System.out.println("data = " + data);
 }
 // 全部读完之后,会调用该方法
 @Override
 public void doAfterAllAnalysed(AnalysisContext context) {
 // TODO......
 }
}

2、最简单的写(导出)

2.1 需求、准备工作
/**
 * 需求:单实体导出(从应用程序将文件导入到磁盘)
 * 导出多个学生对象到Excel表格
 * 包含如下列:姓名、性别、出生日期
 * 模板详见:学员信息.xlsx
 */
2.2、编写导出数据的实体
// 还是上个实体类...
2.3、 准备数据并写入到文件
/**
 * 测试写出数据
 */
 @Test
 public void simpleWrite() {
 // 创造数据
 ArrayList list = new ArrayList( );
 list.add(new Student("张三","男",new Date(),"1001"));
 list.add(new Student("李四","女",new Date(),"1002"));
 list.add(new Student("王五","男",new Date(),"1003"));
 list.add(new Student("赵六","女",new Date(),"1004"));
 list.add(new Student("周期","男",new Date(),"1005"));
 list.add(new Student("茅十八","女",new Date(),"1006"));
 // 写出的文件路径,当前项目下
 String fileName = "student.xlsx";
 // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
 // 如果这里想使用03 则 传入excelType参数即可
 EasyExcel.write(fileName, Student.class).sheet("学生信息").doWrite(list);
 System.out.println("导出OK" );
 }

三、vue文件上传和下载[重点]

基于springboot的文件上传和下载

0. 导入依赖

3.1 文件上传

注意: 本地Excel表格,列要和数据库一致

需求: 批量插入计量单位(导入excel数据到项目中)

思路:

  • 前端设计文件上传组件 ,点击开始文件上传
  • 后端接收文件,使用工具解析数据
  • 插入数据库

3.1.1 前端

在页面设计上传的组件

1.设置上传按钮,显示上传的对话框

2.设置对话框+上传组件


 
 
 
 将文件拖到此处,或点击上传
 只能上传.xlsx,.xls文件,且不超过500kb
 
 
uploadExcelSuccess(){
 this.$message({
 type:"success",
 message:"上传成功"
 })
 this.dialogExcelVisible = false;
 this.fetchData();
},
uploadExcelError(err){
 this.$message({
 type:"error",
 message:err
 })
}
3.1.2 后端-实体类注解

3.1.3 后端-监听器
package com.qf.config;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.qf.entity.SysUser;
import com.qf.entity.UnitMeasure;
import com.qf.service.SysUserService;
import com.qf.service.UnitMeasureService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
@Component
@Scope("prototype") // 作者要求每次读取都要使用新的Listener
public class MdUnitMeasureReadListener extends AnalysisEventListener {
 // 有个很重要的点 DemoDataListener 不能被spring管理,
 // 要每次读取excel都要new,然后里面用到spring可以构造方法传进去
 private UnitMeasureService service;
 public MdUnitMeasureReadListener(){} // 空参构造
 // 有参构造
 public MdUnitMeasureReadListener(UnitMeasureService service){
 this.service = service;
 }
 // 每隔5条存储数据库,实际项目中使用时可以100条,然后清理list ,方便内存回收
 private final int BATCH_COUNT = 5;
 private ArrayList list = new ArrayList( );
 // 每读一行,会调用该invoke方法一次
 @Override
 public void invoke(