赞
踩
●Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。 但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。
●EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部载到内存中,而是从磁盘上一行行读取数据,逐个解析。
●EasyExcel采用一 行一 行的解析模式,并将一行的解析结果以观察者的模式通知处理( AnalysisEventListener )。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.18</version>
</dependency>
@Data public class UserDO { //设置excel表头名称 @ExcelProperty("用户编号") @ColumnWidth(20) private Long id; /** * 设置该列的名称为”用户名“; * */ @ExcelProperty("用户名") /** * 设置表格列的宽度为20; * */ @ColumnWidth(20) private String username; /** * 导出时忽略该字段 * */ @ExcelIgnore private String password; @ExcelProperty("昵称") @ColumnWidth(20) private String nickname; @ExcelProperty("生日") @ColumnWidth(20) /** * 按照指定的格式对日期进行格式化; * */ @DateTimeFormat("yyyy-MM-dd") private Date birthday; @ExcelProperty("手机号") @ColumnWidth(20) private String phone; @ExcelProperty("身高(米)") @NumberFormat("#.##") @ColumnWidth(20) private Double height; /** * 自定义内容转换器 * */ @ExcelProperty(value = "性别", converter = GenderConverter.class) @ColumnWidth(10) private Integer gender; }
常用注解有:
@ExcelProperty 指定当前字段对应excel中的哪一列。可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,以此类推。千万注意,要么全部不写,要么全部用index,要么全部用名字去匹配。千万别三个混着用,除非你非常了解源代码中三个混着用怎么去排序的。
@ExcelIgnore EasyExcel默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
@DateTimeFormat 日期转换,用String去接收excel日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat
@NumberFormat 数字转换,用String去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat
通过自定义转换器,比如将数据库中表示性别的1、0转换成男、女的实例:
性别转换器:
/** * 性别转换器 * */ public class GenderConverter implements Converter<Integer> { @Override public Class<?> supportJavaTypeKey() { // 实体类中对象属性类型 return Integer.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { // Excel中对应的CellData(单元格数据)属性类型 return CellDataTypeEnum.STRING; } /** * 将单元格里的数据转为java对象,也就是女转成2,男转成1,用于导入excel时对性别字段进行转换 * */ @Override public Integer convertToJavaData(ReadConverterContext<?> context) throws Exception { // 从CellData中读取数据,判断Excel中的值,将其转换为预期的数值 return GenderEnum.convert(context.getReadCellData().getStringValue()).getValue(); } /** * 将java对象转为单元格数据,也就是2转成女,1转成男,用于导出excel时对性别字段进行转换 * */ @Override public WriteCellData<?> convertToExcelData(WriteConverterContext<Integer> context) throws Exception { // 判断实体类中获取的值,转换为Excel预期的值,并封装为CellData对象 return new WriteCellData<>(GenderEnum.convert(context.getValue()).getDescription()); } }
4、性别枚举类
@Getter @AllArgsConstructor public enum GenderEnum { /** * 未知 */ UNKNOWN(0, "未知"), /** * 男性 */ MALE(1, "男性"), /** * 女性 */ FEMALE(2, "女性"); private final Integer value; @JsonFormat private final String description; public static GenderEnum convert(Integer value) { // 用于为给定元素创建顺序流 // values:获取枚举类型的对象数组 return Stream.of(values()) .filter(bean -> bean.value.equals(value)) .findAny() .orElse(UNKNOWN); } public static GenderEnum convert(String description) { return Stream.of(values()) .filter(bean -> bean.description.equals(description)) .findAny() .orElse(UNKNOWN); } }
/** * 设置响应结果 * * @param response 响应结果对象 * @param rawFileName 文件名 * @throws UnsupportedEncodingException 不支持编码异常 */ private void setExcelResponseProp(HttpServletResponse response, String rawFileName) throws UnsupportedEncodingException { //设置内容类型 response.setContentType("application/vnd.vnd.ms-excel"); //设置编码格式 response.setCharacterEncoding("utf-8"); //设置导出文件名称(避免乱码) String fileName = URLEncoder.encode(rawFileName.concat(".xlsx"), "UTF-8"); // 设置响应头 response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName); } private Date getBirthday(int year, int month, int day){ Calendar calendar = Calendar.getInstance(); calendar.set(year, month, day); return calendar.getTime(); } /** * 导出数据 * */ @GetMapping("/export/user") public void exportUserExcel(HttpServletResponse response) throws IOException { OutputStream outputStream=response.getOutputStream(); try { this.setExcelResponseProp(response, "用户列表"); // 模拟根据条件在数据库查询数据 List<UserDO> userList = new ArrayList<>(); for(int i=0;i<30;i++){ UserDO userDO =new UserDO(); userDO.setBirthday(getBirthday(2001,1,i)); userDO.setGender(1); userDO.setHeight(Double.valueOf(i)); userDO.setId(Long.valueOf(i)); userDO.setPhone("138"+i); userDO.setNickname("yuanhaoz"); userDO.setPassword("5849"+i); userDO.setUsername("monky"+i); userList.add(userDO); } //这个实现方式非常简单直接,使用EasyExcel的write方法将查询到的数据进行处理,以流的形式写出即可 EasyExcel.write(outputStream,UserDO.class)//对应的导出实体类 .excelType(ExcelTypeEnum.XLSX)//excel文件类型,包括CSV、XLS、XLSX .sheet("用户列表")//导出sheet页名称 .doWrite(userList); //查询获取的数据集合List<T>,转成excel } catch (IOException e) { throw new RuntimeException(e); }finally { outputStream.flush(); outputStream.close(); } }
/** * 多sheet导出数据 * */ @GetMapping("/manySheet") public void exportManySheet(HttpServletResponse response)throws IOException{ OutputStream outputStream=response.getOutputStream(); ExcelWriter writer = EasyExcel.write(outputStream, UserDO.class).excelType(ExcelTypeEnum.XLSX).build(); try { this.setExcelResponseProp(response, "用户列表"); // 模拟根据条件在数据库分页查询数据 for(int j=1;j<=5;j++){ List<UserDO> userList = new ArrayList<>(); for(int i=0;i<30;i++){ UserDO userDO =new UserDO(); userDO.setBirthday(getBirthday(2001,1,i)); userDO.setGender(1); userDO.setHeight(Double.valueOf(i)); userDO.setId(Long.valueOf(i)); userDO.setPhone("138"+i); userDO.setNickname("yuanhaoz"+i); userDO.setPassword("5849"+i); userDO.setUsername("monky"+i); userList.add(userDO); System.out.println(i); } //创建新的sheet页 WriteSheet writeSheet = EasyExcel.writerSheet("用户信息" + j).build(); //将list集合中的对象写到对应的sheet中去 writer.write(userList,writeSheet); } } catch (IOException e) { throw new RuntimeException(e); //给提示todo }finally { writer.finish(); outputStream.flush(); outputStream.close(); } }
/** * 自定义监听器,对下载的excel中的数据进行校验 * */ public class UserListener extends AnalysisEventListener { List<String> names = new ArrayList<>(); /** * 每解析一行,回调该方法 * * @param data * @param context */ @Override public void invoke(Object data, AnalysisContext context) { //校验名称 String name = ((UserDO) data).getUsername(); if (StrUtil.isBlank(name)) { throw new RuntimeException(String.format("第%s行名称为空,请核实", context.readRowHolder().getRowIndex() + 1)); } if (names.contains(name)) { throw new RuntimeException(String.format("第%s行名称已重复,请核实", context.readRowHolder().getRowIndex() + 1)); } else { names.add(name); } } /** * 出现异常回调 * * @param exception * @param context * @throws Exception */ @Override public void onException(Exception exception, AnalysisContext context) throws Exception { if (exception instanceof ExcelDataConvertException) { /**从0开始计算*/ Integer columnIndex = ((ExcelDataConvertException) exception).getColumnIndex() + 1; Integer rowIndex = ((ExcelDataConvertException) exception).getRowIndex() + 1; String message = "第" + rowIndex + "行,第" + columnIndex + "列" + "数据格式有误,请核实"; throw new RuntimeException(message); } else if (exception instanceof RuntimeException) { throw exception; } else { super.onException(exception, context); } } /** * 解析完,全部回调 * * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { //解析完,全部回调逻辑实现 names.clear(); } }
/** * 导入数据 * */ @PostMapping(value = "/importData") public void importData(MultipartFile file){ try { //获取文件的输入流 InputStream inputStream = file.getInputStream(); List<UserDO> lst = EasyExcel.read(inputStream) //调用read方法 //注册自定义监听器,字段校验可以在监听器内实现 .registerReadListener(new UserListener()) .head(UserDO.class) //对应导入的实体类 .sheet(0) //导入数据的sheet页编号,0代表第一个sheet页,如果不填,则会导入所有sheet页的数据 .headRowNumber(1) //列表头行数,1代表列表头有1行,第二行开始为数据行 .doReadSync(); //开始读Excel,返回一个List<T>集合,继续后续入库操作 //模拟导入数据库操作 for (UserDO userDO:lst){ System.out.println(userDO.toString()); } }catch (IOException exception){ throw new RuntimeException(exception); } }
因为UserListener对象不能交给spring对象进行管理、需要手动new出来,所以不能注入其他对象,比如Service
那么如何解决呢?
可以在UserListener中添加xxxService属性并创建有参构造器和无参构造器。
Service层导入Excel的方法参数里添加需要使用的Service实现对象
例如
ResultUtil importData(MultipartFile file, SubjectService subjectService )
以及
EasyExcel.read(inputStream)
.registerReadListener(new UserListener(subjectService ))
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。