EasyExcel使用实体类进行读操作和写操作

一、EasyExcel概述

1.1 EasyExcel的基本作用

  • 数据导入:减轻录入工作量

  • 数据导出:统计信息归档

  • 数据传输:异构系统之间数据传输

1.2 其它解析框架存在的问题

Java领域解析、生成Excel比较有名的框架有Apache poijxl等。

但它们都存在一个严重的问题就是非常的耗内存。

如果系统并发量不大的话可能还行,但是一旦并发上来后一定会出现OOM或者JVM频繁的full gc

1.3 EasyExcel的优势

EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称

EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中。

而是从磁盘上一行行读取数据,逐个解析。

EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者模式通知处理(AnalysisEventListener)。

综上所述:

EasyExcel是一个基于Java的简单又省内存的读写Excel的开源项目。

在尽可能节约内存的情况下支持读写百M大小的Excel文件。

1.4 官方文档地址

https://alibaba-easyexcel.github.io/index.html

1.5 github地址

https://github.com/alibaba/easyexcel

二、引入依赖

<dependencies>
    
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>easyexcelartifactId>
        <version>3.1.2version>
    dependency>
dependencies>

三、EasyExcel的写操作

3.1 创建实体类

// 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 编写生成数据的方法

//循环设置要添加的数据,最终封装到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 将数据写入Excel中的单个sheet文件

写法一(普通api):

public static void main(String[] args) throws Exception {
    // 指定文件位置
    String fileName = "D:\\test.xlsx";
    // 指定各个参数并向指定位置写出一个Excel文件
    EasyExcel.write(fileName, Student.class).sheet(0, "学生列表").doWrite(data());
    
}

写法二(lambda表达式):

public static void main(String[] args) throws Exception {
    // 指定文件位置
    String fileName = "D:\\test.xlsx";
    EasyExcel.write(fileName, Student.class).sheet("学生列表")
             .doWrite(() -> {
                 // 这个函数中返回需要的数据即可
                 return data();
             });
}

所传参数详解:

  • write方法需要指定文件位置需要指定写用哪个class去写,写入数据的位置默认是第一个sheet

  • sheet方法需要指定当前写入数据的那个sheet的名称(必须)和向第几个sheet写入数据(非必须)

  • doWrite方法可以接收需要写入的数据的集合或者传入一个lambda表达式,表达式的返回值依旧是数据的集合

3.4 将数据写入Excel中的多个sheet文件

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();
}

四、EasyExcel在web应用中的写操作(下载)

web应用中,经常会碰到Excel导出(下载)的操作。

EasyExcel中主要使用HttpServletResponse对象获取输出流把文件写出到浏览器。

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

五、Excel读操作

5.1 创建实体类

// 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 创建读取操作的监听器

1)基础版

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) {
    }
}

注意:

自定义的监听器类不能被spring管理,每次读取excel都要new一个新的对象。

如果里面用到spring管理的对象,可以使用构造方法传递进来:

// 如果用到了就在监听器中加上类似代码,去使用dao层或者service层中的逻辑把读取到的数据写到数据库中
private DemoDAO demoDAO;

public DemoDataListener(DemoDAO demoDAO) {
    this.demoDAO = demoDAO;
}

2)需要用到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 读取Excel文件中单个sheet的数据

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 读取excel文件的多个sheet的数据

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();
    }
}

六、EasyExcel在web应用中的的读操作(上传)

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

你可能感兴趣的:(EasyExcel,java,jvm,开发语言,数据库)