赞
踩
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());
- }
- }
-
- @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> list = 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 : list){
- System.out.println(userDO.toString());
- }
-
- } catch (IOException exception){
- throw new RuntimeException(exception);
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。