前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何用一行代码实现excel导入导出

如何用一行代码实现excel导入导出

作者头像
lyb-geek
发布2020-01-16 11:15:47
2.5K0
发布2020-01-16 11:15:47
举报

前言

早期我们实现excel导入导出的技术方案,可能会不假思索的选用Apache poi、jxl。但他们存在内存消耗大,编码相对繁琐。好在现在阿里开源了EasyExcel,看它的名字大概就可以猜出来,这框架的一个特点就是容易使用。其次easyExcel省内存。下图是使用easyexcel消耗的内存图

下边就带大家了解一下easyexcel

正文

1、easyExcel官方介绍

快速、简单避免OOM的java处理Excel工具

ps:其底层仍然是基于poi,不过做了优化

2、官网

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

3、easyExcel官方提供的API文档

https://alibaba-easyexcel.github.io/quickstart/api.html

4、easyExcel官方提供的demo例子

https://github.com/alibaba/easyexcel/tree/master/src/test/java/com/alibaba/easyexcel/test/demo

5、使用easyExcel过程中,可能遇到的问题

https://alibaba-easyexcel.github.io/quickstart/faq.html

6、截止当前版本2.1.4的easyexcel不支持以下功能

  • 单个文件的并发写入、读取
  • 读取图片
  • csv读取

不得不说阿里在开源这方面确实做得很好,文档啥的一应齐全。大家只要按上方介绍的进行操作,基本上就可以很好的使用easyExcel了。好像介绍到这边,这篇文章就可以结束了,不过既然我标题都写了一行代码实现导入导出,那我就来演示一下这个操作,对了,阿里目前的demo还没有提供导入字段校验,下边我也演示一下如何利用hibernate-validator与easyExcel集成实现字段校验

demo演示

1、pom.xml

<dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.0.17.Final</version>
    </dependency>
     <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>easyexcel</artifactId>
      <version>2.1.4</version>
    </dependency>

2、编写实体

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ExcelDemoEntity implements Serializable {

  private static final long serialVersionUID = 1L;

  /**
   * 用户ID
   */
  @ExcelProperty(value="用户ID")
  private Long userId;

  /**
   * 用户名
   */
  @NotBlank(message="用户名不能为空",groups = ExcelGroup.class)
  @ExcelProperty(value="用户名")
  private String username;

  /**
   * 密码
   */
  @NotBlank(message="密码不能为空", groups = AddGroup.class)
  @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
  @ExcelIgnore
  private String password;


  /**
   * 邮箱
   */
  @NotBlank(message="邮箱不能为空",groups = ExcelGroup.class)
  @Email(message="邮箱格式不正确",groups = ExcelGroup.class)
  @ExcelProperty(value = "邮箱")
  private String email;

  /**
   * 手机号
   */
  @ExcelProperty(value="手机号")
  private String mobile;

  /**
   * 状态  0:禁用   1:正常
   */
  @ExcelProperty(value="状态",converter = ExcelDemoEntityStatusConverter.class)
  private Integer status;


  /**
   * 创建时间
   */
  @ExcelProperty(value="创建时间")
  private Date createTime;


  /**
   * 部门名称
   */
  @NotNull(message="部门不能为空",groups = ExcelGroup.class)
  @ExcelProperty(value="部门名称")
  private String deptName;

}
导入

2、实现ReadListener监听器

@Slf4j
public class BaseAnalysisEventListener<T> extends AnalysisEventListener<T> {

    @Getter
    private ExcelData<T> excelData;

    private List<T> rows = new ArrayList<>();

    private List<ErrorExcelRow> errorExcelRows = new ArrayList<>();


    public BaseAnalysisEventListener() {
        excelData = new ExcelData<>();
    }

    /**
     * excel一行行解析
     * @param entity
     * @param analysisContext
     */
    @Override
    public void invoke(T entity, AnalysisContext analysisContext) {

        boolean isPass = this.validatePass(entity,analysisContext);
        if(isPass){
            rows.add(entity);
        }
    }

    /**
     * excel数据行都解析完毕时调用
     * @param analysisContext
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        excelData.setErrorRows(errorExcelRows);
        excelData.setRows(rows);
    }


    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception
     * @param analysisContext
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext analysisContext) {
        log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
        ErrorExcelRow errorExcelRow = this.setAndReturnErrorExcelRow(analysisContext,exception.getMessage());
        errorExcelRows.add(errorExcelRow);
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
                excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());

        }
    }

    /**
     * 这里为一行行的返回头
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
    }

    private boolean validatePass(T entity, AnalysisContext analysisContext) {
        ValidResult validResult = ValidatorUtils.allCheckValidate(entity,false, ExcelGroup.class);
        if(!validResult.isSuccess()){
            String errorMsg = StringUtils.join(validResult.getErrorMessages(),",");
            ErrorExcelRow errorExcelRow = this.setAndReturnErrorExcelRow(analysisContext,errorMsg);
            errorExcelRows.add(errorExcelRow);
        }

        return validResult.isSuccess();
    }


    private ErrorExcelRow setAndReturnErrorExcelRow(AnalysisContext analysisContext,String errorMsg){
        int rowIndex = analysisContext.readRowHolder().getRowIndex();
        int sheetNo = analysisContext.readSheetHolder().getSheetNo();
        String sheetName = analysisContext.readSheetHolder().getSheetName();
        ErrorExcelRow errorExcelRow = ErrorExcelRow.builder()
            .rowNum(rowIndex).sheetName(sheetName).sheetNo(sheetNo)
            .errorMessage(errorMsg).build();

        return errorExcelRow;
    }



}

ps:这段代码实现看起来和阿里的demo实现不大一样,主要是考虑要是每个entity读取每次都要搞这么一个监听器,太繁琐,我这边就实现了一个通用的监听器,只做数据校验和列表整合,把真正的业务逻辑实现外移到业务层去做

3、校验核心代码

public static <T> ValidResult allCheckValidate(T obj,boolean isShowField, Class<?>... groups) {
        Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj,groups);
        return getValidResult(isShowField, constraintViolations);
    }



    private static <T> ValidResult getAllCheckValidResult(boolean isShowField,
        Set<ConstraintViolation<T>> constraintViolations) {

        //返回异常result
        if (constraintViolations.size() > 0) {
            List<String> errorMessages = new LinkedList<>();
            Iterator<ConstraintViolation<T>> iterator = constraintViolations.iterator();
            while (iterator.hasNext()) {
                ConstraintViolation<T> violation = iterator.next();
                if(isShowField){
                    errorMessages.add(String.format("%s:%s", violation.getPropertyPath().toString(), violation.getMessage()));
                }else{
                    errorMessages.add(violation.getMessage());
                }

            }
            return ValidResult.fail(errorMessages);
        }
        return ValidResult.success();
    }

4、导入工具类核心方法

public <T> ExcelData<T> read(Class<T> entityClass) {
        BaseAnalysisEventListener<T> listener = new BaseAnalysisEventListener<>();
        EasyExcel.read(inputStream,entityClass,listener).sheet(sheetNo).headRowNumber(headRowNumber).doRead();
        return listener.getExcelData();
    }

    public <T> ExcelData<T> readAll(Class<T> entityClass) {
        BaseAnalysisEventListener<T> listener = new BaseAnalysisEventListener<>();
        EasyExcel.read(inputStream,entityClass,listener).headRowNumber(headRowNumber).doReadAll();
        return listener.getExcelData();
    }

5、web层调用

ExcelData<ExcelDemoEntity> excelDemoEntityExcelData = ExcelReader.builder().inputStream(file.getInputStream()).headRowNumber(1).sheetNo(0).build().read(ExcelDemoEntity.class);

就一行代码就实现导入

6、效果图演示

利用postMan来演示

a、校验信息不通过

b、校验通过

导出

导出其实比较简单,直接用easyExcel提供的write方法就搞定了,也是一行代码。不过我习惯性会浅浅的封装一层,其封装核心代码如下

public <T> void write(List<T> entityClassList, Class<T> entityClass) throws Exception{
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding("utf-8");
    fileName = URLEncoder.encode(fileName, "UTF-8");
    response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
    EasyExcel.write(response.getOutputStream(),entityClass).sheet(sheetNo).doWrite(entityClassList);
  }

web层调用

ExcelWriter.builder().fileName("员工花名册").response(response).sheetNo(0).build().write(excelDemoEntities,ExcelDemoEntity.class);

除了获取业务集合,其真正实现导出的代码也是只有一行

效果图展示

总结

上边的示例只是简单的演示一下easyexcel的功能,easyexcel提供的功能远不止上面的功能,比如实体字段和excel字段映射转换、根据自定义模板导出、多个工作表解析等等。这些功能阿里的demo都有相关示例,感兴趣的朋友可以查看如下链接

https://github.com/alibaba/easyexcel

进行学习了解。由于我项目使用的easyexcel版本,对一些复杂的数据读取,支持并不怎么友好。后边我又整合了另一个excel框架--easypoi,它可以支持比较复杂的excel读取,easypoi的相关教程,就不在本篇论述了。感兴趣的朋友可以查看如下链接

http://easypoi.mydoc.io/

进行学习了解,同时我底下提供的demo,浅浅的封装了easypoi+easyexcel,其中easypoi用来读取,easyexcel用来写,核心代码基本上也是一行就搞定导入导出。大家如果感兴趣的话,也可以fork下来蛮看下

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-easyexcel

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linyb极客之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文
    • 1、easyExcel官方介绍
      • 2、官网
        • 3、easyExcel官方提供的API文档
          • 4、easyExcel官方提供的demo例子
            • 5、使用easyExcel过程中,可能遇到的问题
              • 6、截止当前版本2.1.4的easyexcel不支持以下功能
                • demo演示
                  • 导入
                  • 导出
                • 总结
                  • 进行学习了解,同时我底下提供的demo,浅浅的封装了easypoi+easyexcel,其中easypoi用来读取,easyexcel用来写,核心代码基本上也是一行就搞定导入导出。大家如果感兴趣的话,也可以fork下来蛮看下
                    • demo链接
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档