SpringBoot集成EasyExcel入门

SpringBoot集成EasyExcel入门,第1张

概述
  • 项目地址:github.com/alibaba/eas…
  • 官方文档:www.yuque.com/easyexcel/d…
  • EasyExcel是一款阿里开源的Excel导入导出工具,具有处理快速、占用内存小、使用方便的特点,在Github上已有22k+Star,可见其非常流行
  • EasyExcel读取75M(46W行25列)的Excel,仅需使用64M内存,耗时20s,极速模式还可以更快!
读Excel

常用注解:

  • @ExcelProperty 指定当前字段对应excel中的那一列。可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,以此类推。千万注意,要么全部不写,要么全部用index,要么全部用名字去匹配。千万别三个混着用,除非你非常了解源代码中三个混着用怎么去排序的。
  • @ExcelIgnore 默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
  • @DateTimeFormat 日期转换,用String去接收excel日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat
  • @NumberFormat 数字转换,用String去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat
  • @ExcelIgnoreUnannotated默认不加ExcelProperty 的注解的都会参与读写,加了不会参与
单Sheet
  • 依赖:等下demo有用到lombok和fastjson,所以这里我加上了

     
    <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>easyexcelartifactId>
                <version>3.0.5version>
     dependency>
    
    
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>fastjsonartifactId>
        <version>1.2.80version>
    dependency>
    
    
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <version>1.18.2version>
        <scope>compilescope>
    dependency>
    
  • 实体类,转换类

    public class SysExcelDemo implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        @ExcelIgnore
        @TableId(value = "id", type = IdType.AUTO)
        private Integer id;
    
        @ExcelProperty(value = "用户名")
        private String userName;
    
        @ExcelProperty(value = "手机号")
        private String mobile;
    
        @ExcelProperty(value = "性别", converter = SexExcelConverter.class)
        private Integer sex;
    }
    
    public class SexExcelConverter implements Converter<Integer> {
        @Override
        public Integer convertToJavaData(ReadConverterContext<?> context) throws Exception {
            //获取excel性别的值
            String value = context.getReadCellData().getStringValue();
            if ("男".equals(value)) {
                return 1;
            } else {
                return 0;
            }
        }
    
    }
    
  • 监听器类,这个是重点

    //有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
    @Slf4j
    public class DemoDataListener implements ReadListener<SysExcelDemo> {
    
        //每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
        private static final int BATCH_COUNT = 100;
    
        //缓存的数据
        private List<SysExcelDemo> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    
        //Service层
        private ISysExcelDemoService demoService;
    
    
        public DemoDataListener(ISysExcelDemoService demoService){
            this.demoService = demoService;
        }
    
        private static final String[] headers = {"用户名", "手机号", "性别"};
    
        //验证模板
        @Override
        public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
            if (context.readRowHolder().getRowIndex() == 0) {
                for (int i = 0; i < headers.length; i++) {
                    if(!headers[i].equals(headMap.get(i).getStringValue())){
                        throw new ExcelAnalysisException("请导入正确的模板");
                    }
                }
            }
        }
        
        //这个每一条数据解析都会来调用,
        //这里也可以使用不创建对象的读invoke(Map data, AnalysisContext context),当我们这里验证数据之后再封装进我们的实体
        @Override
        public void invoke(SysExcelDemo data, AnalysisContext analysisContext) {
            log.info("解析到一条数据:{}", JSON.toJSONString(data));
            cachedDataList.add(data);
            // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
            if (cachedDataList.size() >= BATCH_COUNT) {
                saveData();
                // 存储完成清理 list
                cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
            }
        }
        
        //在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
        @Override
        public void onException(Exception exception, AnalysisContext context) {
            //数据转换异常
            if (exception instanceof ExcelDataConvertException) {
                ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
                log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
                        excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
            }
            //解析异常
            if(exception instanceof ExcelAnalysisException){
                ExcelAnalysisException excelAnalysisException = (ExcelAnalysisException) exception;
                String message = excelAnalysisException.getMessage();
                log.error(message);
                //抛出异常,停止解析
                throw new ExcelAnalysisStopException(message);
            }
        }
        
        //存储数据库
        private void saveData() {
            log.info("{}条数据,开始存储数据库!", cachedDataList.size());
            demoService.saveBatch(cachedDataList);
            log.info("存储数据库成功!");
        }
        
        
        //所有数据解析完成了 都会来调用
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            // 这里也要保存数据,确保最后遗留的数据也存储到数据库
            saveData();
            log.info("所有数据解析完成!");
        }
    
    }
    
  • controller

    @Controller
    @RequestMapping("/sysExcelDemo")
    public class SysExcelDemoController {
    
    
        @Autowired
        private ISysExcelDemoService demoService;
        
        @PostMapping("/excelRead")
        @ResponseBody
        public AjaxResult read(@RequestParam(name = "file", required = true) MultipartFile file) throws IOException {
    
            EasyExcel.read(file.getInputStream(), SysExcelDemo.class, new DemoDataListener(demoService)).sheet().doRead();
            return AjaxResult.suc();
        }
    }
    

测试,导入我们准备的xlsx

  • 可以看到,数据库存储了excel中的数据

  • 当我们导入的标题头跟监听器我们定义的headers不一致时(模板不一致),就不会继续解析下一行了,并会打印出错误日志

多Sheet

下面直接使用官方的demo,就是创建了多个监听器读取不同的sheet

    /**
     * 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件
     * 

* 1. 创建excel对应的实体对象 参照{@link DemoData} *

* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} *

* 3. 直接读即可 */ @Test public void repeatedRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 读取全部sheet,这里其实就是同一个监听器去接收全部Sheet // 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll(); // 读取部分sheet fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; ExcelReader excelReader = null; try { excelReader = EasyExcel.read(fileName).build(); // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener ReadSheet readSheet1 = EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build(); ReadSheet readSheet2 = EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build(); // 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能 excelReader.read(readSheet1, readSheet2); } finally { if (excelReader != null) { // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 excelReader.finish(); } } }

写Excel

我们现在刚才导入数据库的数据导出到excel中,还原一下

  • 还是刚才的实体

    • 这里主要新加了一个注解@ColumnWidth(50),指定手机号列的宽度,还可以指定行高等
    • 转换类重写了convertToExcelData方法,数据库到excel的转换
    @TableName("sys_excel_demo")
    @ApiModel(value = "SysExcelDemo对象", description = "")
    public class SysExcelDemo implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        @ExcelIgnore
        @TableId(value = "id", type = IdType.AUTO)
        private Integer id;
    
        @ExcelProperty(value = "用户名")
        private String userName;
        
        @ColumnWidth(50)
        @ExcelProperty(value = "手机号")
        private String mobile;
        
        @ExcelProperty(value = "性别", converter = SexExcelConverter.class)
        private Integer sex;
    }
    
    public class SexExcelConverter implements Converter<Integer> {
    
        @Override
        public Integer convertToJavaData(ReadConverterContext<?> context) throws Exception {
            //获取excel性别的值
            String value = context.getReadCellData().getStringValue();
            if ("男".equals(value)) {
                return 1;
            } else {
                return 0;
            }
        }
    
        @Override
        public WriteCellData<?> convertToExcelData(WriteConverterContext<Integer> context) throws Exception {
            Integer sex = context.getValue();
            if(sex == 1){
                return new WriteCellData<>("男");
            }else {
                return new WriteCellData<>("女");
            }
        }
    }
    
  • controller

    @Controller
    @RequestMapping("/sysExcelDemo")
    public class SysExcelDemoController {
    
        @Autowired
        private ISysExcelDemoService demoService;
    
        @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(), SysExcelDemo.class).sheet("Sheet1").doWrite(demoService.list());
        }
    
    }
    

准备完成,开始测试,直接调用download接口,可以看到,跟刚才导入的数据一致

总结

官方除了读写excel,还有填充excel,可以去官方文档看看,但是很多细节,还有出现的bug,看官方文档是远远不够的,需要自己去实践一下。其实还有挺多坑的

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/916473.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存