springboot使用EasyExcel实现Excel导入导出

news/2024/7/21 3:51:00 标签: excel

         java生成Excel比较有名的框架有Apache poi、jxl等,但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。

        EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部载到内存中,而是从磁盘上一行行读取数据,逐个解析。EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理( AnalysisEventListener )。

1、pom添加easyexcel依赖

<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>

2、实体类UserDO与Excel数据对应

@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

3、converter自定义转换器

自定义转换器将数据库中表示性别的1、0转换成男、女

  3.1、GenderConverter 

/**
 * 性别转换器
 * */
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());
    }
}

 3.2、GenderEnum

@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);
    }

}

4、导出

/**
 * 设置响应结果
 *
 * @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();
	}
}

5、多sheet导出

/**
 * 多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();

	}
}

6、导入

6.1、UserListener

/**
 * 自定义监听器,对下载的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();
    }
}

6.2、ImportData

/**
 * 导入数据
 * */
@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);
	}
}


http://www.niftyadmin.cn/n/5415619.html

相关文章

精读《React Conf 2019 - Day2》

1 引言 这是继 精读《React Conf 2019 - Day1》 之后的第二篇&#xff0c;补充了 React Conf 2019 第二天的内容。 2 概述 & 精读 第二天的内容更为精彩&#xff0c;笔者会重点介绍比较干货的部分。 Fast refresh Fast refresh 是更好的 react-hot-loader 替代方案&am…

CNC机加工引入复合机器人可以提高生产效率,降低成本

CNC加工企业在过去依赖大量的人工来完成生产线上的各项任务&#xff0c;包括CNC机床的上下料、物料搬运以及部分装配工作。然而&#xff0c;随着产能需求的不断增长和人工成本的持续上升&#xff0c;企业逐渐意识到自动化升级的重要性与迫切性。 面临的挑战与需求&#xff1a; …

基于SSM技术的宠物寄存系统设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术介绍 3 1.1 开发技术语言 3 1.1.1 Java 3 1.1.2 Ajax 3 1.1.3 JavaScript 3 1.2 开发框架 4 1.2.1 Spring 4 1.2.2 Spring MVC 4 1.2.3 Mybatis 4 1.2.4 Bootstrap 5 1.3 MySQL数据库 5 1.4 本章小结 6 2 系统分析 7 2.1 系统的需…

最简k8s部署(AWS Load Balancer Controller使用)

问题 我需要在k8s集群里面部署springboot服务&#xff0c;通过k8s ingress访问集群内部的springboot服务&#xff0c;应该怎么做&#xff1f; 这里假设已经准备好k8s集群&#xff0c;而且也准备好springboot服务的运行镜像了。这里我们将精力放在k8s服务编排上面。 一图胜千言…

机器学习--循环神经网络(RNN)1

一、简介 循环神经网络&#xff08;Recurrent Neural Network&#xff09;是深度学习领域中一种非常经典的网络结构&#xff0c;在现实生活中有着广泛的应用。以槽填充&#xff08;slot filling&#xff09;为例&#xff0c;如下图所示&#xff0c;假设订票系统听到用户说&…

图片编辑器tui-image-editor

提示&#xff1a;图片编辑器tui-image-editor 文章目录 前言一、安装tui-image-editor二、新建components/ImageEditor.vue三、修改App.vue四、效果五、遇到问题 this.getResolve is not a function总结 前言 需求&#xff1a;图片编辑器tui-image-editor 一、安装tui-image-ed…

Springboot+vue的物业管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的物业管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的物业管理系统&#xff0c;采用M&#xff08;model&#xff09;V&#xff…

基于YOLOv8深度学习的智能道路裂缝检测与分析系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标检测、目标分割

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…