广告位联系
返回顶部
分享到

java怎么在项目中实现excel导入导出功能

java 来源:互联网 作者:佚名 发布时间:2024-10-08 22:33:16 人浏览
摘要

一、初识EasyExcel* 1. Apache POI 先说POI,有过报表导入导出经验的同学,应该听过或者使用。 Apache POI是Apache软件基金会的开源函式库,提供跨平台的Java API实现Microsoft Office格式档案读写。但是存

一、初识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。

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

特点

  • 功能强大

  • 代码书写冗余繁杂

  • 读写大文件耗费内存较大,容易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多一点)

  • 在上层做了模型转换的封装,让使用者更加简单方便

特点

  • 在数据模型层面进行了封装,使用简单
  • 重写了07版本的Excel的解析代码,降低内存消耗,能有效避免OOM
  • 只能操作Excel
  • 不能读取图片

二、快速入门–QuickStart

0、导入依赖坐标

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<!-- EasyExcel -->

<dependency>

    <groupId>com.alibaba</groupId>

    <artifactId>easyexcel</artifactId>

    <version>2.1.6</version>

</dependency>

<!-- lombok 优雅编程 -->

<dependency>

    <groupId>org.projectlombok</groupId>

    <artifactId>lombok</artifactId>

    <version>1.16.20</version>

</dependency>

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-test</artifactId>

</dependency>

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

1、最简单的读

1.1、需求、准备工作

1

2

3

4

5

6

/**

 * 需求:单实体导入(从磁盘将文件导入到应用程序)

 * 导入Excel学员信息到系统。

 * 包含如下列:姓名、性别、出生日期

 * 模板详见:学员信息.xlsx

 */

学员信息.xlsx

1.2、编写导出数据的实体类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

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文件(上传)

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

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的监听器,用于处理读取产生的数据

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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<Student> {

    // 每读一行,会调用该invoke方法一次

    @Override

    public void invoke(Student data, AnalysisContext context) {

        System.out.println("data = " + data);

    }

 

    // 全部读完之后,会调用该方法

    @Override

    public void doAfterAllAnalysed(AnalysisContext context) {

        // TODO......

    }

}

2、最简单的写(导出)

2.1 需求、准备工作

1

2

3

4

5

6

/**

 * 需求:单实体导出(从应用程序将文件导入到磁盘)

 * 导出多个学生对象到Excel表格

 * 包含如下列:姓名、性别、出生日期

 * 模板详见:学员信息.xlsx

 */

2.2、编写导出数据的实体

1

// 还是上个实体类...

2.3、 准备数据并写入到文件

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

/**

 * 测试写出数据

 */

@Test

public void simpleWrite() {

    // 创造数据

    ArrayList<Student> 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. 导入依赖

1

2

3

4

5

<!-- EasyExcel -->

 

<!-- lombok -->

 

<!-- junit -->

3.1 文件上传

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

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

思路:

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

3.1.1 前端

在页面设计上传的组件

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

<!-- 上传excel的对话框 -->

<el-dialog title="上传计量单位Excel" :visible.sync="dialogExcelVisible" width="40%">

  <el-upload

    class="upload-demo"

    drag

    action="http://localhost:8888/md/unit/upload/excel"

    accept=".xlsx,.xls"

    :on-success="uploadExcelSuccess"

    :on-error="uploadExcelError"

    multiple>

    <i class="el-icon-upload"></i>

    <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>

    <div class="el-upload__tip" slot="tip">只能上传.xlsx,.xls文件,且不超过500kb</div>

  </el-upload>

</el-dialog>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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 后端-监听器

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

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<UnitMeasure> {

    // 有个很重要的点 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<UnitMeasure> list = new ArrayList<>( );

 

    // 每读一行,会调用该invoke方法一次

    @Override

    public void invoke(UnitMeasure data, AnalysisContext context) {

        list.add(data);

        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM

        if (list.size( ) >= BATCH_COUNT) {

            // 调用方法,执行插入

            saveData( );

            // 存储完成清理 list

            list.clear();

        }

    }

 

    // 全部读完之后,会调用该方法

    @Override

    public void doAfterAllAnalysed(AnalysisContext context) {

        // 这里也要保存数据,确保最后遗留的数据也存储到数据库

        if(list.size() > 0) {

            saveData( );

        }

        System.out.println("所有数据解析完成!");

    }

 

 

    // 向数据库插入数据

    private void saveData() {

        System.out.println("开始存储数据库!");

 

        // 调用业务层存数据库

        service.saveBatch(list);

        System.out.println("存储数据库成功!");

    }

}

3.1.4 后端-Controller接收

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

/**

    * 上传Excel文件

    */

   @PostMapping("/upload/excel")

   public R uploadExcel(MultipartFile file) {

       try {

           // 重点,此处第三个参数需要传入Service对象

           EasyExcel.read(file.getInputStream( ),

                           UnitMeasure.class,

                           new MdUnitMeasureReadListener(service))

                   .sheet( ).doRead( );

       }catch (Exception e){

           e.printStackTrace();

           return R.fail(e.getMessage());

       }

       return R.ok( );

   }

3.1.5 后端-Service

1

// 需要添加批量插入的方法

3.1.6 后端-Mapper

1

2

3

4

5

6

7

8

9

10

11

12

13

   <!-- 批量插入 -->

<insert id="saveBatch">

    insert into

        md_unit_measure       (measure_code,measure_name,primary_flag,primary_id,change_rate,enable_flag)

    values

    <foreach collection="list" item="unit" separator=",">

        (#{unit.measureCode},

        #{unit.measureName},

        #{unit.primaryFlag},

        #{unit.primaryId},

        #{unit.changeRate},

        #{unit.enableFlag})

    </foreach>

3.1.7 测试

本地创建一个excel表格,按照实体类中定义的列名填充数据

点击上传

3.2 文件导出

需求: 将项目中的数据导出到本地excel表格

思路:

  • 前端设计按钮,点击开始导出
  • 后端请求,查询出数据,
  • 使用工具导出

实体类于之前一样

1

// 略

Controller层

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

/**

 * 下载Excel文件

 * 注意,返回值是void

 */

@GetMapping("/download/excel")

public void downloadExcel(HttpServletResponse response) throws IOException {

 

    //  查询数据库全部数据

    List<UnitMeasure> list = service.findAll(null);

 

    // 下面这个注释是前端使用方式1,即a标签发请求时采用

    response.setContentType("application/vnd.ms-excel");

    response.setCharacterEncoding("utf-8");

    // 这里URLEncoder.encode可以防止中文乱码

    String fileName = URLEncoder.encode("计量单位信息", "UTF-8");

    response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");

 

    // easyexcel导出工具

    EasyExcel.write(response.getOutputStream(), UnitMeasure.class).autoCloseStream(Boolean.FALSE).sheet("计量单位信息")

        .doWrite(list);

}

// todo servicce+mapper实现查询全部

前端

1.导出按钮触发函数

2.函数内确认导出,发出请求到后端

3.3、自定义单元格样式

EasyExcel支持调整行高、列宽、背景色、字体大小等内容,但是控制方式与使用原生POI无异,比较繁琐,不建议使用。

但是可以使用模板填充的方式,向预设样式的表格中直接写入数据,写入数据的时候会保持原有样式。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计