• 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

    1.5 github地址

    https://github.com/alibaba/easyexcel
    
    • 1

    二、引入依赖

    <dependencies>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>easyexcelartifactId>
            <version>3.1.2version>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    三、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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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());
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    写法二(lambda表达式):
    public static void main(String[] args) throws Exception {
        // 指定文件位置
        String fileName = "D:\\test.xlsx";
        EasyExcel.write(fileName, Student.class).sheet("学生列表")
                 .doWrite(() -> {
                     // 这个函数中返回需要的数据即可
                     return data();
                 });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    所传参数详解:

    • 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    四、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;
        }
    }
    
    • 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
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    五、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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    注意:

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

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

    // 如果用到了就在监听器中加上类似代码,去使用dao层或者service层中的逻辑把读取到的数据写到数据库中
    private DemoDAO demoDAO;
    
    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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("存储数据库成功!");
        }
    }
    
    • 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

    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();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    六、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;
        }
    }
    
    • 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
  • 相关阅读:
    轻量级的VsCode为何越用越大?为什么吃了我C盘10G?如何无痛清理VsCode缓存?手把手教你为C盘瘦身
    vue手动拖入和导入excel模版
    YOLOv5全面解析教程②:如何制作训练效果更好的数据集
    Django + Nginx https部署实战(第一辑)
    RK3399平台开发系列讲解(内存篇)15.33、为什么可用内存会远超物理内存?
    redis缓存一致性讨论
    PyCharm鼠标控制字体缩放
    计算机毕业设计之java+ssm果蔬经营平台系统
    【杂谈】仿生人会梦见电子羊吗?
    PaddleNLP使用Vicuna
  • 原文地址:https://blog.csdn.net/qq_44749491/article/details/127860995