SpringBoot导出Excel的四种实现方式
作者:雷袭月启
背景
近期接到了一个小需求,要将系统中的数据导出为Excel,且能将Excel数据导入到系统。对于大多数研发人员来说,这算是一个最基本的操作了。但是……我居然有点方!
好多年没有实操这种基础的功能了。我对于excel导入导出的印象还停留在才入行时的工作经历。配模板,建对应的实体类,写输入输出流…代码繁琐而低效,且不能很好的支持Excel中的格式调整、行列合并等各种复杂操作,每次回想起这些,我都会有一种深深的无力感 。然,研发技术的更新换代堪称日新月异。这么多年过去了,也该有更优雅的方式实现这些功能了吧!我翻了下自己这些年积累下来的屎山代码,也参考了公司几位大佬的手段,将现有项目中实现的Excel导入导出方案整理之后,居然发现,一个小小的公司里竟有如此多的卧龙凤雏,连这种基操都能鼓捣出三种以上的方法来。
方法一:来自胡大佬的馈赠
第一种方法来自于胡大佬的推荐,大佬说这方法也不是他原创的,而是早年他自某个项目中偶然觅得,奉为圭臬,忍不住收藏下来(说白了就是从别的项目里COPY的),该方法通过自研的一套Excel注解,在实体类上添加注解,然后用一套工具类(见源码中com.leixi.excel.util.excelOne)实现实体类列表的导出。优点是不用建模板,调用简单,缺点是迁移麻烦。有好几个配套的文件如注解,工具类等,想要在别的项目中使用,就涉及到文件的拷贝。老实说,不太优雅,而且更可惜的是,这些方法里居然没有封装导出方法。下面给出实体类创建,方法调用的代码,供各位参考(相关源码会在文中提供)
//引入依赖 <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.12</version> </dependency> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-base</artifactId> <version>4.3.0</version> </dependency>
//controller方法,不得不说,调用起来是真的方便 @PostMapping(value = "/exportData") public ResponseEntity<byte[]> exportData(String fileName) { List<ExcelOneDto> list = CommonUtil.buildDemoExcel(ExcelOneDto.class); Workbook workbook = new DefaultWriteHandler<ExcelOneDto>().write("sheet名称",list, ExcelOneDto.class); return CommonUtil.exportWorkbook(workbook, fileName); } //实体类 @Data public class ExcelOneDto { @ExcelCell(priority = "A", cellTitle = "编号") private Integer code; @ExcelCell(priority = "B", cellTitle = "名称") private String name; @ExcelCell(priority = "C", cellTitle = "详情") private String desc; @ExcelCell(priority = "D", cellTitle = "备注") private String remark; }
这是导出的测试结果:
方法二:ExcelImportUtil工具类
第二种方法是我从现有项目中找到的,通过使用ExcelImportUtil的importExcel和exportExcel实现数据的导入导出。大概是同事抄得不够高明,初次看到这个方法时,我觉得蛮臃肿的,但是在经过迁移和封装后,代码量减了不少,用起来还挺方便,而且该方法还支持Excel导出,以及嵌套的实体导出,可以说是十分高明了。该方法的优点:不用excel模板,导出导出方便。缺点是封装的不够好。研发人员使用时需要对ExcelImportUtil进行一定的加工。下面是我封装后的代码:
//maven依赖 <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-base</artifactId> <version>4.3.0</version> </dependency>
//control类的导入导出 @PostMapping(value = "/importData") public Object importManageData(@RequestPart("file") MultipartFile file) throws Exception { return CommonUtil.getExcelDataFromFile(file, ExcelTwoDto.class, 1,2); } @PostMapping(value = "/exportData") public ResponseEntity<byte[]> exportData(@RequestParam("fileName") String fileName){ List<ExcelTwoDto> list = CommonUtil.buildDemoExcel(ExcelTwoDto.class); List<Map<String, Object>> exportView = CommonUtil.buildExportView("测试Title", "测试Sheet",list); Workbook workbook = ExcelExportUtil.exportExcel(exportView, ExcelType.XSSF); ExcelStyleUtil.addNotExistCell(workbook, 0, 3); return CommonUtil.exportWorkbook(workbook, fileName); } //实体类 @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ExcelTwoDto { @Excel(name = "第一列" ,fixedIndex = 0, orderNum = "0") private String codeOne; @Excel(name = "第二列" ,fixedIndex = 1, orderNum = "1", width = 50) private String codeTwo; @ExcelCollection(name = "第三列" , orderNum = "2") private List<ExcelChildDto> codeThree; @Excel(name = "第四列" ,fixedIndex = 4, orderNum = "4", width = 50) private String codeFour; @Excel(name = "第五列" ,fixedIndex = 5, orderNum = "5") private String codeFive; @Excel(name = "第六列A", groupName = "第六列汇总" ,fixedIndex = 6, orderNum = "6") private String codeThreeOne; @Excel(name = "第六列B", groupName = "第六列汇总" ,fixedIndex = 7, orderNum = "7") private String codeThreeTwo; @Excel(name = "第六列C", groupName = "第六列汇总" ,fixedIndex = 8, orderNum = "8") private String codeThreeThree; } @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ExcelChildDto { @Excel(name = "编码" ,fixedIndex = 0, orderNum = "0") private String code; @Excel(name = "名称" ,fixedIndex = 1, orderNum = "1", width = 50) private String name; }
导出测试结果:
方法三:Excel模板导出
第三种方案是我从之前的屎山代码中翻出来的,通过excel模板来实现的方法,现在基本已经废弃了,之所以放在这里是为了做一个参照,让大家看看不同的实现方法的区别。这种方式通过创建一个Excel模板和对应模板的实体类来实现文件导出,优点是实体类里不需要加注解了,而且在excel里可以设计一些style和格式,缺点是配置excelTemplate太麻烦了。配置繁琐,且过程中如出现参数名错误,符号错误等 ,调试起来很不方便,如果要增减字段,实体类和excel都得调整。下面给出编写的代码:
// controller方法 @PostMapping(value = "/exportData") public ResponseEntity<byte[]> exportData(@RequestParam("fileName") String fileName) { TemplateExportParams params = new TemplateExportParams("templates/template.xlsx"); params.setScanAllsheet(true); Map<String, Object> dataMap = new HashMap<>(); List<ExcelThreeDto> list = CommonUtil.buildDemoExcel(ExcelThreeDto.class); dataMap.put("titleName", "标题名称"); dataMap.put("desc", "备注名称"); dataMap.put("dataList", list); Workbook workbook = ExcelExportUtil.exportExcel(params, dataMap); ExcelStyleUtil.setAutoHeight(workbook, 0, 4); return CommonUtil.exportWorkbook(workbook, fileName); } //实体类 @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ExcelThreeDto { private Integer code; private String name; private String desc; private String remark; }
Excel模板的配置
导出的结果:
由于年代久远,我没能找到对应的模板导入的方法,于是在网上搜索了相关资料,编写了一个较通用的文件导入方法:
//maven依赖 <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-base</artifactId> <version>4.3.0</version> </dependency>
/** * excel 导入 */ @PostMapping("/importData") public Object upload(@RequestParam(name = "file") MultipartFile file) { return CommonUtil.importExcel(file, 3, ExcelThreeDto.class); } /** * 通用的excel导入的方法 * * @param file 导入文件 * @param startRow 第几行开始读数据 * @param clazz 导入后转成的实体类 * @return * @param <T> */ @SneakyThrows public static <T> List<T> importExcel(MultipartFile file, Integer startRow, Class<T> clazz) { Workbook wb = new XSSFWorkbook(file.getInputStream()); //1.2.获取Sheet Sheet sheet = wb.getSheetAt(0); List<T> list = new ArrayList<>(); Field[] fields = clazz.getDeclaredFields(); for (int rowNum = startRow; rowNum <= sheet.getLastRowNum(); rowNum++) { //根据索引获取每一个行 Row row = sheet.getRow(rowNum); T obj = clazz.newInstance(); for (int cellNum = 0; cellNum < row.getLastCellNum() && cellNum< fields.length; cellNum++) { Cell cell = row.getCell(cellNum); Object value = CommonUtil.getCellValue(cell); Field field = fields[cellNum]; field.setAccessible(true); // 注意,这里是为了演示,只分析了Integer和String的写法,实际上要根据Field数据的枚举分类处理 if (field.getType().equals(Integer.class) && value != null) { field.set(obj, new Double(value.toString()).intValue()); } else { field.set(obj, value); } } list.add(obj); } return list; }
导入的结果还是蛮令人满意的:
方法四:easyexcel
以上这些都是我从以往的项目中见识到的Excel导入导出方法,那么,时下流行的方式是什么样的呢?网上搜索资料得知,现在使用频率较高的是用easyexcel实现的。经过一番了解,我将easyexcel的实现方式也列出来作为参考。该方法的优点是使用方便,功能强大!本文中我只实现了最简单的导入导出,easyexcel还支持很多如嵌套导入,合并行列等高级操作
//引入maven依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.1.3</version> </dependency>
//controller: @PostMapping(value = "/exportData") public void exportData(String fileName, HttpServletResponse response) { try { this.setExcelResponseProp(response, fileName); List<ExcelFourDto> list = CommonUtil.buildDemoExcel(ExcelFourDto.class);; EasyExcel.write(response.getOutputStream()) .head(ExcelFourDto.class) .excelType(ExcelTypeEnum.XLSX) .sheet(fileName) .doWrite(list); } catch (IOException e) { throw new RuntimeException(e); } } @PostMapping("/importData") @SneakyThrows public Object importUserExcel(@RequestPart("file") MultipartFile file) { List<ExcelFourDto> list = EasyExcel.read(file.getInputStream()) .head(ExcelFourDto.class).sheet().doReadSync(); return list; } /** * 设置响应结果 * * @param response 响应结果对象 * @param rawFileName 文件名 * @throws UnsupportedEncodingException 不支持编码异常 */ private void setExcelResponseProp(HttpServletResponse response, String rawFileName) throws UnsupportedEncodingException { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String fileName = URLEncoder.encode(rawFileName, "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); } // 实体类 @Data public class ExcelFourDto { @ExcelProperty("编号") @ColumnWidth(20) private Integer code; @ExcelProperty("名称") @ColumnWidth(20) private String name; @ExcelProperty("详情") @ColumnWidth(20) private String desc; @ExcelProperty("备注") @ColumnWidth(20) private String remark; }
以下是测试结果:
原以为胡大佬的方法已经是别开生面了,但是对比起来,仍然是落伍了很多(希望胡大佬不要看到这篇博客)。时代真是进步的飞快,对于程序员来说,这种变迁显得尤为明显。它就像一根鞭子,不断的抽打着码农们,稍有懈怠就会被这洪流所淹没…
(文中附上四种方式的源码,仅用于学习和参考!)
以上就是SpringBoot导出Excel的四种实现方式的详细内容,更多关于SpringBoot导出Excel的资料请关注脚本之家其它相关文章!