EasyExcel 官方文档 - 基于 Java 的 Excel 处理工具 | Easy Excel
# 搭建过程
# 导入 pom 依赖
| <dependency> |
| <groupId>com.alibaba</groupId> |
| <artifactId>easyexcel</artifactId> |
| <version>3.3.2</version> |
| </dependency> |
# web 上传、下载
# 官方 demo 代码
| |
| * 文件下载(失败了会返回一个有部分数据的 Excel) |
| * <p> |
| * 1. 创建 excel 对应的实体对象 参照 {@link DownloadData} |
| * <p> |
| * 2. 设置返回的 参数 |
| * <p> |
| * 3. 直接写,这里注意,finish 的时候会自动关闭 OutputStream, 当然你外面再关闭流问题不大 |
| */ |
| @GetMapping("download") |
| public void download(HttpServletResponse response) throws IOException { |
| |
| response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); |
| response.setCharacterEncoding("utf-8"); |
| |
| String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20"); |
| response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); |
| EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data()); |
| } |
| |
| |
| * 文件上传 |
| * <p>1. 创建 excel 对应的实体对象 参照 {@link UploadData} |
| * <p>2. 由于默认一行行的读取 excel,所以需要创建 excel 一行一行的回调监听器,参照 {@link UploadDataListener} |
| * <p>3. 直接读即可 |
| */ |
| @PostMapping("upload") |
| @ResponseBody |
| public String upload(MultipartFile file) throws IOException { |
| EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead(); |
| return "success"; |
| } |
| package com.alibaba.easyexcel.test.demo.web; |
| |
| import java.util.List; |
| |
| import com.alibaba.excel.context.AnalysisContext; |
| import com.alibaba.excel.read.listener.ReadListener; |
| import com.alibaba.excel.util.ListUtils; |
| import com.alibaba.fastjson2.JSON; |
| |
| import lombok.extern.slf4j.Slf4j; |
| |
| |
| * 模板的读取类 |
| * |
| * @author Jiaju Zhuang |
| */ |
| |
| @Slf4j |
| public class UploadDataListener implements ReadListener<UploadData> { |
| |
| * 每隔 5 条存储数据库,实际使用中可以 100 条,然后清理 list ,方便内存回收 |
| */ |
| private static final int BATCH_COUNT = 5; |
| private List<UploadData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); |
| |
| * 假设这个是一个 DAO,当然有业务逻辑这个也可以是一个 service。当然如果不用存储这个对象没用。 |
| */ |
| private UploadDAO uploadDAO; |
| |
| public UploadDataListener() { |
| |
| uploadDAO = new UploadDAO(); |
| } |
| |
| |
| * 如果使用了 spring, 请使用这个构造方法。每次创建 Listener 的时候需要把 spring 管理的类传进来 |
| * |
| * @param uploadDAO |
| */ |
| public UploadDataListener(UploadDAO uploadDAO) { |
| this.uploadDAO = uploadDAO; |
| } |
| |
| |
| * 这个每一条数据解析都会来调用 |
| * |
| * @param data one row value. It is same as {@link AnalysisContext#readRowHolder ()} |
| * @param context |
| */ |
| @Override |
| public void invoke(UploadData data, AnalysisContext context) { |
| log.info("解析到一条数据:{}", JSON.toJSONString(data)); |
| cachedDataList.add(data); |
| |
| if (cachedDataList.size() >= BATCH_COUNT) { |
| saveData(); |
| |
| cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); |
| } |
| } |
| |
| |
| * 所有数据解析完成了 都会来调用 |
| * |
| * @param context |
| */ |
| @Override |
| public void doAfterAllAnalysed(AnalysisContext context) { |
| |
| saveData(); |
| log.info("所有数据解析完成!"); |
| } |
| |
| |
| * 加上存储数据库 |
| */ |
| private void saveData() { |
| log.info("{}条数据,开始存储数据库!", cachedDataList.size()); |
| uploadDAO.save(cachedDataList); |
| log.info("存储数据库成功!"); |
| } |
| } |
easyexcel/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java at master · alibaba/easyexcel
# 项目实战
# 上传导入
- 导入依赖
| <dependency> |
| <groupId>com.alibaba</groupId> |
| <artifactId>easyexcel</artifactId> |
| <version>3.3.2</version> |
| </dependency> |
- 创建 BusinessLinkUploadListener 类,且实现 ReadListener<BusinessLinkDTO>
- 有个很重要的点 BusinessLinkUploadListener 不能被 spring 管理,要每次读取 excel 都要 new, 然后里面用到 spring 可以构造方法传进去,所以使用有参构造
| |
| * 如果使用了 spring, 请使用这个构造方法。每次创建 Listener 的时候需要把 spring 管理的类传进来 |
| * |
| * @param businessLinkService |
| */ |
| public BusinessLinkUploadListener(BusinessLinkService businessLinkService, HttpServletResponse response) { |
| this.businessLinkService = businessLinkService; |
| |
| this.response = response; |
| } |
- 设置多少条数据之后执行一次存储
| |
| * 每隔 100 条存储数据库,然后清理 list ,方便内存回收 |
| */ |
| private static final int BATCH_COUNT = 100; |
监听器方法
- void invoke (BusinessLinkDTO businessLinkDTO, AnalysisContext context) 方法(这个每一条数据解析都会来调用)
| |
| * 这个每一条数据解析都会来调用 |
| * |
| * @param businessLinkDTO |
| * @param context |
| */ |
| @Override |
| public void invoke(BusinessLinkDTO businessLinkDTO, AnalysisContext context) { |
| log.info("解析到一条数据:{}", JSONObject.toJSONString(businessLinkDTO)); |
| cachedDataList.add(businessLinkDTO); |
| |
| if (cachedDataList.size() >= BATCH_COUNT) { |
| saveData(); |
| |
| cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); |
| } |
| } |
- void doAfterAllAnalysed (AnalysisContext context) 方法 (所有数据解析完成了 都会来调用)
| |
| * 所有数据解析完成了 都会来调用 |
| * |
| * @param context |
| */ |
| @Override |
| public void doAfterAllAnalysed(AnalysisContext context) { |
| |
| saveData(); |
| log.info("所有数据解析完成!"); |
| |
| failList = new ArrayList<>(); |
| } |
业务处理方法(在 invoke 方法处理时进行做业务数据处理)
| |
| * 保存数据库 |
| */ |
| @SneakyThrows |
| private void saveData() { |
| log.info("{}条数据,开始存储数据库!", cachedDataList.size()); |
| for (BusinessLinkDTO businessLinkDTO : cachedDataList) { |
| String s = checkParam(businessLinkDTO); |
| if (!StringUtils.isEmpty(s)){ |
| UploadFailBusinessLinkDTO dto = new UploadFailBusinessLinkDTO(); |
| BeanUtils.copyProperties(businessLinkDTO,dto); |
| dto.setFailMsg(s); |
| failList.add(dto); |
| continue; |
| } |
| businessLinkService.saveBusinessLink(businessLinkDTO); |
| } |
| if(failList.size() > 0){ |
| EasyExcel |
| .write(response.getOutputStream(), UploadFailBusinessLinkDTO.class) |
| .sheet("业务链接") |
| .doWrite(failList); |
| } |
| log.info("存储数据库成功!"); |
| } |
Controller 代码
| |
| * 导入 |
| * @param file |
| * @return |
| * @throws IOException |
| */ |
| @PostMapping("/upload") |
| @ResponseBody |
| public String upload(MultipartFile file,HttpServletResponse response) throws IOException { |
| EasyExcel.read( |
| file.getInputStream(), |
| BusinessLinkDTO.class, |
| new BusinessLinkUploadListener(businessLinkService,response)) |
| .sheet() |
| .doRead(); |
| return "success"; |
| } |
# 下载导出
- 实体代码
| |
| * @Description: 业务链接 DTO |
| * @Author: Guo.Yang |
| * @Date: 2023/07/03/09:30 |
| */ |
| @Data |
| @TableName("business_link_b") |
| public class BusinessLinkDTO { |
| |
| * 主键 |
| */ |
| @ExcelIgnore |
| private Integer id; |
| |
| * 业务名称 |
| */ |
| @ExcelProperty(value = "业务名称",index = 0) |
| @NotBlank(message = "业务名称不可未空!") |
| private String businessName; |
| |
| * 业务链接 |
| */ |
| @ExcelProperty(value = "业务链接",index = 1) |
| @NotBlank(message = "业务链接不可未空!") |
| private String businessUrl; |
| |
| * 类型 |
| */ |
| @ExcelProperty(value = "类型",index = 2) |
| private String type; |
| |
| * 备注 |
| */ |
| @ExcelProperty(value = "备注",index = 3) |
| private String remark; |
| |
| * 创建时间 |
| */ |
| @ExcelProperty(value = "创建时间",index = 4) |
| @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| private Date crteTime; |
| |
| * 修改时间 |
| */ |
| @ExcelProperty(value = "修改时间",index = 5) |
| @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| private Date updtTime; |
| |
| * 有效标识 |
| */ |
| @ExcelProperty(value = "有效标识",index = 6) |
| private Integer enableFlag; |
| |
| } |
- Controller 代码
| |
| * 导出数据、导出模版 |
| * @param response |
| * @param type |
| * @throws IOException |
| */ |
| |
| |
| @GetMapping("/download/{type}") |
| public void download(HttpServletResponse response,@PathVariable("type") String type) throws IOException { |
| boolean isDate = "data".equals(type); |
| String dataFormat = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); |
| response.setContentType("application/json;charset=UTF-8"); |
| response.setCharacterEncoding("utf-8"); |
| String fileName = URLEncoder.encode(dataFormat + "_业务链接" + ( isDate? "导出": "模版"), "UTF-8").replaceAll("\\+", "%20"); |
| response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); |
| EasyExcel |
| .write(response.getOutputStream(), BusinessLinkDTO.class) |
| .sheet("业务链接") |
| .doWrite(isDate? businessLinkService.queryBusinessLinkList(new BusinessLinkDTO()): new ArrayList<BusinessLinkDTO>()); |
| } |
# 注意
导出的时候实体最好不要继承,因为 A.class extends B.class 后 ,导出的时候,会将 A 类的字段作为导出表格的第一列,使用 @ExcelProperty (value = "业务名称",index = 0) 注解标注列顺序的时候在两个列直接,也会失效,当然在一个类内 各个字段的排序是生效的。