当前位置:   article > 正文

day61_easyexcel_easyexcel 有哪些版本

easyexcel 有哪些版本

EasyExcel

一、初识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多一点)

img

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

二、快速入门–QuickStart

0、导入依赖坐标

<!-- 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>
<!-- junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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

1、最简单的读

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

学员信息.xlsx

image-20230618174203589

学生姓名学生出生日期学生性别
学号002020-03-16
学号012020-03-16
学号022020-03-16
学号032020-03-16
学号042020-03-16
学号052020-03-16
学号062020-03-16
学号072020-03-16
学号082020-03-16
学号092020-03-16
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
  • 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
注解: 文章后面有详解
  • 1
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.Test;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
public class TestEasyExcel {

    /**
     * 测试读取数据
     */
    @Test
    public void testRead() {
        // 读取文件,读取完之后会自动关闭
        /**
         * pathName  文件路径;"d:\\学员信息.xls"
         * head		 每行数据对应的实体;Student.class
         * readListener	读监听器,每读一样就会调用一次该监听器的invoke方法
         * 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();
    }
}
  • 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

读取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<Student> {
    // 每读一行,会调用该invoke方法一次
    @Override
    public void invoke(Student data, AnalysisContext context) {
        System.out.println("data = " + data);
        log.info(data + "保存成功");
    }

    // 全部读完之后,会调用该方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // TODO......
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

2、最简单的写

2.1 需求、准备工作
/**
 * 需求:单实体导出(从应用程序将文件导入到磁盘)
 * 导出多个学生对象到Excel表格
 * 包含如下列:姓名、性别、出生日期
 * 模板详见:学员信息.xlsx
 */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
2.2、编写导出数据的实体
// 还是上个实体类...
  • 1
2.3、 准备数据并写入到文件
    /**
     * 测试写出数据
     */
    @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" );
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

image-20230618182323185

image-20230618182345134

3、web文件上传和下载[重点]

基于SpringMVC的文件上传和下载(以ssm项目为例演示)

0. 导入依赖

<!-- EasyExcel -->

<!-- lombok -->

<!-- junit -->

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>1.7.2</version>
</dependency>
<!-- SpringMVC(Spring) -->

<!-- Servlet -->

<!-- 文件上传 -->

<!-- 其他ssm开发相关的 -->
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
3.1 文件上传

编写excel中每一行对应的实体类

package com.taotie.model;

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 org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;
@HeadRowHeight(20) // 指定列头行高
@ColumnWidth(20) // 指定列宽
public class House {
    @ExcelProperty("编号")
    private int id;
    @ExcelProperty("房屋地址")
    private String address;
    @ExcelProperty("楼层")
    private int floor;
    @ExcelProperty("面积")
    private String area;
    @ExcelProperty("朝向")
    private String dir;
    @ExcelProperty("房间号")
    private int roomNum;
    @ExcelProperty("装修类型")
    private int deco;
    @ExcelProperty("是否双气")
    private int air;
    @ExcelProperty("价格")
    private double price;
    @ExcelProperty("出租状态")
    private int rentStatus;
    @ExcelIgnore
    private String image;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @ExcelProperty("添加时间")
    private Date addTime;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @ExcelProperty("更新时间")
    private Date updateTime;
    @ExcelProperty("数据状态")
    private int status;

   // set get toString
}
  • 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

编写回调监听器HouseReadListener

package com.taotie.util;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.taotie.model.House;
import com.taotie.service.HouseService;
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 HouseReadListener extends AnalysisEventListener<House> {
    // 有个很重要的点 DemoDataListener 不能被spring管理,
    // 要每次读取excel都要new,然后里面用到spring可以构造方法传进去
    private HouseService houseService;
    public HouseReadListener(){} // 空参构造

    // 有参构造
    public HouseReadListener(HouseService houseService){
        this.houseService = houseService;
    }

    // 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
    private final int BATCH_COUNT = 5;
    private ArrayList<House> list = new ArrayList<>( );

    // 每读一样,会调用该invoke方法一次
    @Override
    public void invoke(House data, AnalysisContext context) {
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size( ) >= BATCH_COUNT) {
            saveData( );
            // 存储完成清理 list
            list.clear();
        }
    }

    // 全部读完之后,会调用该方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData( );
        System.out.println("所有数据解析完成!");
    }

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

        // 调用业务层存数据库
        houseService.saveBatch(list);
        System.out.println("存储数据库成功!");
    }
}
  • 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

业务代码接口HouseService和实现类HouseServiceImpl

public interface HouseService {
    // ...其他略
    ResultData saveBatch(ArrayList<House> list);
}


@Service
public class HouseServiceImpl implements HouseService{

    @Autowired
    private HouseMapper houseMapper;

    // 其他略
    
    @Override
    public ResultData saveBatch(ArrayList<House> list) {
        boolean isok = houseMapper.saveBatch(list);
        if (isok) {
            return ResultData.ok();
        }
        return ResultData.fail();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

持久层Mapper接口和Mapper映射文件(批量添加)

// 参考https://blog.csdn.net/m0_57781768/article/details/128229047
public interface HouseMapper {
    boolean saveBatch(ArrayList<House> list);
}
  • 1
  • 2
  • 3
  • 4
 <insert id="saveBatch">
        <!-- 没有图片路径,因为excel表格中忽略了 -->
        insert into house (address, floor,roomNum, area, dir,deco, air, price, rentStatus, addTime, updateTime,status)
        VALUES
        <foreach collection ="list" item="house" separator =",">
            (#{house.address}, #{house.floor},#{house.roomNum}, #{house.area},
            #{house.dir},#{house.deco}, #{house.air}, #{house.price},
            #{house.rentStatus},#{house.addTime}, #{house.updateTime},#{house.status})
        </foreach>
    </insert>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

SpringMVC配置文件

<!-- 组件扫描-->
<context:component-scan base-package="com.taotie.controller"/>

<!-- MVC文件上传多部件解析器 -->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
      id="multipartResolver"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

编码读取上传的Excel文件

package com.taotie.controller;

import com.alibaba.excel.EasyExcel;
import com.taotie.model.House;
import com.taotie.service.HouseService;
import com.taotie.util.HouseReadListener;
import com.taotie.util.ResultData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
@RestController
@RequestMapping("/house")
public class HouseController {

    @Autowired
    private HouseService houseService;
    // 其他略

    /**
     * 上传解析excel
     * 参数是MultipartFile,参数名固定file,
     * 因为layui文件上传组件渲染的名字叫file
     */
    @RequestMapping("/upload/excel")
    public ResultData uploadExcel(MultipartFile file) throws IOException {
        // 别忘了给HouseReadListener构造方法传入houseService对象
        EasyExcel.read(file.getInputStream(),House.class,new HouseReadListener(houseService)).sheet().doRead();
        return ResultData.ok();
    }
}
  • 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

前端页面

<!-- 设置上传excel表格按钮 -->
<script type="text/html" id="toolbarDemo">
 <div class="layui-btn-container">
  <button class="layui-btn layui-btn-normal layui-btn-sm data-add-btn" lay-event="add"> 添加</button>
  <button class="layui-btn layui-btn-sm layui-btn-danger data-delete-btn" lay-event="deleteBatch"> 删除</button>
  <button id="uploadExcel" class="layui-btn layui-btn-sm layui-btn-normal"> 导入excel </button>
 </div>
</script>
<!-- layui上传组件 -->
<script>
 // 此段代码要放在table.render的下方,先渲染表格,再渲染表格里面的组件
 // 文件上传
    upload.render({
        elem: '#uploadExcel' //绑定上传按钮元素
        , url: '/house/upload/excel.do' //上传接口
        ,accept:'file'  // 接收任意文件类型,默认只能接收图片类型
        ,exts:'xlsx|xls' // 指定接收的文件类型扩展名
        , done: function (res) {  // 上传成功的回调函数
            if (res.code == 0) {
                layer.msg("上传成功!")
            }
            table.reload("currentTableId");
        }
        , error: function () {
            layer.msg("上传失败!")
        }
    });
</script>
  • 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

测试

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

image-20230618194245821

点击上传

image-20230618194429961

查看数据库,上传成功


暂时有个bug,就是第二次上传会失效,这个是layui-upload组件的bug,还没解决

Layui上传文件时choose事件只触发一次的问题 · Issue #I45Z8T · Layui/layui - Gitee.com

3.2 文件下载

ps: layui自带就有导出功能…

自己实现导出全部数据

实体类于之前一样

// 略
  • 1

Controller层

/**
     * 导出excel表格
     */
@RequestMapping("/export/excel")
public void exportExcel(HttpServletResponse response) throws IOException {
    // 调用业务层,查询全部数据
    List<House> list = houseService.findAll4Excel();
    try {
        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.write(response.getOutputStream(), House.class).autoCloseStream(Boolean.FALSE).sheet("房屋信息")
            .doWrite(list);
    } catch (Exception e) {
        // 重置response
        response.reset();
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        Map<String, String> map = new HashMap<String, String>();
        map.put("code", "500");
        map.put("msg", "下载文件失败" + e.getMessage());
        // 需要FastJson依赖
        response.getWriter().println(JSON.toJSONString(map));
    }
}
  • 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

前端

<script type="text/html" id="toolbarDemo">
 <div class="layui-btn-container">
  <button class="layui-btn layui-btn-normal layui-btn-sm data-add-btn" lay-event="add"> 添加</button>
  <button class="layui-btn layui-btn-sm layui-btn-danger data-delete-btn" lay-event="deleteBatch"> 删除
    </button>
  <button id="uploadExcel" class="layui-btn layui-btn-sm layui-btn-normal"> 导入excel</button>
  <a class="layui-btn layui-btn-sm layui-btn-normal" href="/house/export/excel.do"> 导出全部数据
    </a>
    </div>
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4、自定义单元格样式

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

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

三、填充-自学

1、填充一组数据

1.1 准备模板

​ Excel表格中用{} 来表示包裹要填充的变量,如果单元格文本中本来就有{}左右大括号,需要在括号前面使用斜杠转义\{\}

​ 代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。

1.2 封装数据

编写封装填充数据的类或选用Map

/**
 * 使用实体类封装填充数据
 *
 *  实体中成员变量名称需要和Excel表各种{}包裹的变量名匹配
 */
@Data
public class FillData {

    private String name;
    private int age;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
/**
 * 生成多组数据代码
 * / 
    private static List<FillData> initFillData() {
        ArrayList<FillData> fillDatas = new ArrayList<FillData>();
        for (int i = 0; i < 10; i++) {
            FillData fillData = new FillData();
            fillData.setName("学生0" + i);
            fillData.setAge(10 + i);
            fillDatas.add(fillData);
        }
        return fillDatas;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
1.3 填充

准备数据并填充到文件

public static void main(String[] args) {
    // 加载模板
    InputStream templateFile = FillData.class.getClassLoader().getResourceAsStream(
            "fill_data_template1" +
            ".xlsx");

    // 写入文件
    String targetFileName = "单组数据填充.xlsx";

    // 准备对象数据填充
    FillData fillData = new FillData();
    fillData.setName("小熊");
    fillData.setAge(10);


    // 生成工作簿对象
    ExcelWriterBuilder workBookWriter = EasyExcel.write(targetFileName).withTemplate(templateFile);

    // 获取工作表并填充
    //workBookWriter.sheet().doFill(fillData);

    // 使用Map数据填充
    HashMap<String, String> mapFillData = new HashMap<>();
    mapFillData.put("name", "小虎");
    mapFillData.put("age", "11");

    // 获取第一个工作表填充并自动关闭流
    workBookWriter.sheet().doFill(mapFillData);
}
  • 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

1.4 效果

2、填充多组数据

2.1 准备模板

​ Excel表格中用{.} 来表示包裹要填充的变量,如果单元格文本中本来就有{}左右大括号,需要在括号前面使用斜杠转义\{\}

​ 代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。

2.2 封装数据

编写封装填充数据的类或选用Map

// 同上
  • 1
2.3 填充

准备数据并填充到文件

public static void main(String[] args) {
    // 加载模板
    InputStream templateFile = FillData.class.getClassLoader().getResourceAsStream(
            "fill_data_template2.xlsx");

    // 写入文件
    String targetFileName = "多组数据填充.xlsx";

    List<FillData> fillDatas = initData();

    // 生成工作簿对象
    ExcelWriterBuilder workBookWriter =
            EasyExcel.write(targetFileName).withTemplate(templateFile);

    // 获取第一个工作表填充并自动关闭流
    workBookWriter.sheet().doFill(fillDatas);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3、组合填充

3.1 准备模板

即有多组数据填充,又有单一数据填充,为了避免两者数据出现冲突覆盖的情况,在多组填充时需要通过FillConfig对象设置换行。

1584207785924

3.2 封装数据

编写封装填充数据的类或选用Map

// 同上
  • 1
3.3 填充

准备数据并填充到文件

public static void main(String[] args) {

    // 加载模板
    InputStream templateFile = FillData.class.getClassLoader().getResourceAsStream(
            "fill_data_template3.xlsx");

    // 目标文件
    String targetFileName = "组合数据填充.xlsx";

    List<FillData> fillDatas = initData();

    // 生成工作簿对象
    ExcelWriter excelWriter = EasyExcel.write(targetFileName).withTemplate(templateFile).build();

    // 生成工作表对象
    WriteSheet writeSheet = EasyExcel.writerSheet().build();

    // 组合填充时,因为多组填充的数据量不确定,需要在多组填充完之后另起一行
    FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
    // 填充查看生成Excel出问题
	 excelWriter.fill(fillDatas, writeSheet); //查看生成Excel出问题
    // 填充并换行
    //excelWriter.fill(fillDatas, fillConfig, writeSheet);

    HashMap<String, String> otherData = new HashMap<>();
    otherData.put("date", "2022-03-14");
    otherData.put("total", "100");
    excelWriter.fill(otherData, writeSheet);

    // 一定关闭
    excelWriter.finish();
}
  • 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

4、水平填充

4.1 准备模板

水平填充和多组填充模板一样,不一样的地方在于,填充时需要通过FillConfig对象设置水平填充。

4.2 封装数据

编写封装填充数据的类或选用Map

// 同上
  • 1
4.3 填充

准备数据并填充到文件

public static void main(String[] args) {

    // 加载模板
    InputStream templateFile = FillData.class.getClassLoader().getResourceAsStream(
            "fill_data_template4.xlsx");

    // 写入文件
    String targetFileName = "easyExcelDemo\\水平数据填充.xlsx";

    List<FillData> fillDatas = initData();

    // 生成工作簿对象
    ExcelWriter excelWriter = EasyExcel.write(targetFileName).withTemplate(templateFile).build();

    // 生成工作表对象
    WriteSheet writeSheet = EasyExcel.writerSheet().build();


    // 组合填充时,因为多组填充的数据量不确定,需要在多组填充完之后另起一行
    FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();

    // 填充
    excelWriter.fill(fillDatas, fillConfig, writeSheet);

    HashMap<String, String> otherData = new HashMap<>();
    otherData.put("date", "2022-03-14");
    otherData.put("total", "100");
    excelWriter.fill(otherData, writeSheet);

    // 关闭
    excelWriter.finish();
}
  • 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

5、 注意事项

​ 为了节省内存,所以没有采用把整个文档在内存中组织好之后再整体写入到文件的做法,而是采用的是一行一行写入的方式,不能实现删除和移动行,也不支持备注写入。多组数据写入的时候,如果需要新增行,只能在最后一行增加,不能在中间位置添加。

6、填充综合练习

report_template.xlsx

/**
 * reprot综合练习
 */
@Test
public void test06() {

    InputStream templateInputStream = this.getClass().getClassLoader().getResourceAsStream(
            "report_template.xlsx");

    // 目标文件
    String targetFile = "模板写入6-report.xlsx";

    // 写入workbook对象
    ExcelWriter workBook =
            EasyExcel.write(targetFile, FillData.class).withTemplate(templateInputStream).build();

    WriteSheet sheet = EasyExcel.writerSheet().build();

    // 填充配置,开启组合填充换行
    //FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();

    // ****** 准备数据 *******
    // 日期
    HashMap<String, String> dateMap = new HashMap<String, String>();
    dateMap.put("date", "2020-03-16");

    // 总会员数
    HashMap<String, String> totalCountMap = new HashMap<String, String>();
    dateMap.put("totalCount", "1000");

    // 新增员数
    HashMap<String, String> increaseCountMap = new HashMap<String, String>();
    dateMap.put("increaseCount", "100");

    // 本周新增会员数
    HashMap<String, String> increaseCountWeekMap = new HashMap<String, String>();
    dateMap.put("increaseCountWeek", "50");

    // 本月新增会员数
    HashMap<String, String> increaseCountMonthMap = new HashMap<String, String>();
    dateMap.put("increaseCountMonth", "100");


    // 新增会员数据
    List<Student> students = initData();
    // **** 准备数据结束****

    // 写入统计数据
    workBook.fill(dateMap, sheet);
    workBook.fill(totalCountMap, sheet);
    workBook.fill(increaseCountMap, sheet);
    workBook.fill(increaseCountWeekMap, sheet);
    workBook.fill(increaseCountMonthMap, sheet);
    // 写入新增会员
    workBook.fill(students, sheet);
    workBook.finish();

}
  • 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

四、常用API及注解-了解

1、常用类

  • EasyExcel 入口类,用于构建开始各种操作;
  • ExcelReaderBuilder 构建出一个ReadWorkbook对象,即一个工作簿对象,对应的是一个Excel文件;
  • ExcelWriterBuilder 构建出一个WriteWorkbook对象,即一个工作簿对象,对应的是一个Excel文件;
  • ExcelReaderSheetBuilder 构建出一个ReadSheet对象,即一个工作表的对象,对应的Excel中的每个sheet,一个工作簿可以有多个工作表;
  • ExcelWriterSheetBuilder 构建出一WriteSheet对象,即一个工作表的对象,对应的Excel中的每个sheet,一个工作簿可以有多个工作表;
  • ReadListener 在每一行读取完毕后都会调用ReadListener来处理数据,我们可以把调用service的代码可以写在其invoke方法内部;
  • WriteHandler 在每一个操作包括创建单元格、创建表格等都会调用WriteHandler来处理数据,对使用者透明不可见;
  • 所有配置都是继承的 Workbook的配置会被Sheet继承。所以在用EasyExcel设置参数的时候,在EasyExcel…sheet()方法之前作用域是整个sheet,之后针对单个sheet。

2、读取时的注解

@ExcelProperty

使用位置:标准作用在成员变量上

可选属性:

属性名含义说明
index对应Excel表中的列数默认-1,建议指定时从0开始
value对应Excel表中的列头
converter成员变量转换器自定义转换器需要实Converter接口

使用效果:index属性可以指定当前字段对应excel中的哪一列,可以根据列名value去匹配,也可以不写。

如果不使用@ExcelProperty注解,成员变量从上到下的顺序,对应表格中从左到右的顺序;

**使用建议:**要么全部不写,要么全部用index,要么全部用名字去匹配,尽量不要三个混着用。

代码演示:

// 1. 修改成员变量顺序读取Excel表格
// 2. 修改index属性值读取Excel表格
// 3. 修改value属性值读取Excel表格
  • 1
  • 2
  • 3
@ExcelIgnore

标注在成员变量上,默认所有字段都会和excel去匹配,加了这个注解会忽略该字段

代码演示:

// 4. 忽略id成员变量值读取Excel表格
  • 1
@DateTimeFormat

标注在成员变量上,日期转换,代码中用String类型的成员变量去接收excel中日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat

// 5. 按照指定的格式写入Excel内容
  • 1
@NumberFormat

标注在成员变量上,数字转换,代码中用String类型的成员变量去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat

@ExcelIgnoreUnannotated

标注在类上。

不标注该注解时,默认类中所有成员变量都会参与读写,无论是否在成员变量上加了@ExcelProperty 的注解。

标注该注解后,类中的成员变量如果没有标注@ExcelProperty 注解将不会参与读写。

3、 读取时通用参数

ReadWorkbook,ReadSheet 都会有的参数,如果为空,默认使用上级。

  • converter 转换器,默认加载了很多转换器。也可以自定义。

  • readListener 监听器,在读取数据的过程中会不断的调用监听器。

  • headRowNumber 指定需要读表格的 列头行数。默认有一行头,也就是认为第二行开始起为数据。

  • headclazz二选一。读取文件头对应的列表,会根据列表匹配数据。建议使用clas,就是文件中每一行数据对应的代码中的实体类型。

  • clazzhead二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据。

  • autoTrim 字符串、表头等数据自动trim

  • password 读的时候是否需要使用密码

4、ReadWorkbook(工作簿对象)参数

  • excelType 当前excel的类型,读取时会自动判断,无需设置。
  • inputStreamfile二选一。建议使用file。
  • fileinputStream二选一。读取文件的文件。
  • autoCloseStream 自动关闭流。
  • readCache 默认小于5M用 内存,超过5M会使用 EhCache,不建议使用这个参数。
  • useDefaultListener @since 2.1.4 默认会加入ModelBuildEventListener 来帮忙转换成传入class的对象,设置成false后将不会协助转换对象,自定义的监听器会接收到Map<Integer,CellData>对象,如果还想继续接听到class对象,请调用readListener方法,加入自定义的beforeListenerModelBuildEventListener、 自定义的afterListener即可。

5、ReadSheet(工作表对象)参数

  • sheetNo 需要读取Sheet的编号,建议使用这个来指定读取哪个Sheet
  • sheetName 根据名字去匹配Sheet,excel 2003不支持根据名字去匹配

6、写入时的注解注解

@ExcelProperty

使用位置:标准作用在成员变量上

可选属性:

属性名含义说明
index对应Excel表中的列数默认-1,指定时建议从0开始
value对应Excel表中的列头
converter成员变量转换器自定义转换器需要实Converter接口

使用效果index 指定写到第几列,如果不指定则根据成员变量位置排序;

value指定写入的列头,如果不指定则使用成员变量的名字作为列头;

​ 如果要设置复杂的头,可以为value指定多个值。

代码演示:

    // 5. 为《学员表.xlsx》文件中学生信息设置一个统一的表头“学员信息表”
  • 1
其他注解:

基本和读取时一致

  • @ContentRowHeight() 标注在类上或属性上,指定内容行高

  • @HeadRowHeight() 标注在类上或属性上,指定列头行高

  • @ColumnWidth() 标注在类上或属性上,指定列宽

  • ExcelIgnore` 默认所有字段都会写入excel,这个注解会忽略这个字段

  • DateTimeFormat 日期转换,将Date写到excel会调用这个注解。里面的value参照java.text.SimpleDateFormat

    是com.alibaba.excel.annotation.format.DateTimeFormat;包下的

  • NumberFormat 数字转换,用Number写excel会调用这个注解。里面的value参照java.text.DecimalFormat

  • ExcelIgnoreUnannotated 默认不加 ExcelProperty 的注解的都会参与读写,加了不会参与

7、写入时通用参数

WriteWorkbookWriteSheet都会有的参数,如果为空,默认使用上级。

  • converter 转换器,默认加载了很多转换器。也可以自定义。

  • writeHandler 写的处理器。可以实现WorkbookWriteHandler,SheetWriteHandler,RowWriteHandler,CellWriteHandler,在写入excel的不同阶段会调用,对使用者透明不可见。

  • relativeHeadRowIndex 距离多少行后开始。也就是开头空几行

  • needHead 是否导出头

  • headclazz二选一。写入文件的头列表,建议使用class。

  • clazzhead二选一。写入文件的头对应的class,也可以使用注解。

  • autoTrim 字符串、表头等数据自动trim

8、WriteWorkbook(工作簿对象)参数

  • excelType 当前excel的类型,默认为xlsx

  • outputStreamfile二选一。写入文件的流

  • fileoutputStream二选一。写入的文件

  • templateInputStream 模板的文件流

  • templateFile 模板文件

  • autoCloseStream 自动关闭流。

  • password 写的时候是否需要使用密码

  • useDefaultStyle 写的时候是否是使用默认头

9、WriteSheet(工作表对象)参数

  • sheetNo 需要写入的编号。默认0

  • sheetName 需要些的Sheet名称,默认同sheetNo

头行高

  • @ColumnWidth() 标注在类上或属性上,指定列宽

  • ExcelIgnore` 默认所有字段都会写入excel,这个注解会忽略这个字段

  • DateTimeFormat 日期转换,将Date写到excel会调用这个注解。里面的value参照java.text.SimpleDateFormat

    是com.alibaba.excel.annotation.format.DateTimeFormat;包下的

  • NumberFormat 数字转换,用Number写excel会调用这个注解。里面的value参照java.text.DecimalFormat

  • ExcelIgnoreUnannotated 默认不加 ExcelProperty 的注解的都会参与读写,加了不会参与

7、写入时通用参数

WriteWorkbookWriteSheet都会有的参数,如果为空,默认使用上级。

  • converter 转换器,默认加载了很多转换器。也可以自定义。

  • writeHandler 写的处理器。可以实现WorkbookWriteHandler,SheetWriteHandler,RowWriteHandler,CellWriteHandler,在写入excel的不同阶段会调用,对使用者透明不可见。

  • relativeHeadRowIndex 距离多少行后开始。也就是开头空几行

  • needHead 是否导出头

  • headclazz二选一。写入文件的头列表,建议使用class。

  • clazzhead二选一。写入文件的头对应的class,也可以使用注解。

  • autoTrim 字符串、表头等数据自动trim

8、WriteWorkbook(工作簿对象)参数

  • excelType 当前excel的类型,默认为xlsx

  • outputStreamfile二选一。写入文件的流

  • fileoutputStream二选一。写入的文件

  • templateInputStream 模板的文件流

  • templateFile 模板文件

  • autoCloseStream 自动关闭流。

  • password 写的时候是否需要使用密码

  • useDefaultStyle 写的时候是否是使用默认头

9、WriteSheet(工作表对象)参数

  • sheetNo 需要写入的编号。默认0

  • sheetName 需要些的Sheet名称,默认同sheetNo

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/170236
推荐阅读
相关标签
  

闽ICP备14008679号