springboot使用EasyExcel实现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数据对应

java 复制代码
@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

java 复制代码
/**
 * 性别转换器
 * */
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

java 复制代码
@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、导出

java 复制代码
/**
 * 设置响应结果
 *
 * @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导出

java 复制代码
/**
 * 多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

java 复制代码
/**
 * 自定义监听器,对下载的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

java 复制代码
/**
 * 导入数据
 * */
@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);
	}
}
相关推荐
@PHARAOH8 小时前
HOW - 在浏览器下载一个 Excel 表格文件
前端·excel
꧁༺摩༒西༻꧂16 小时前
Python生成Excel
开发语言·python·excel
President~wolf2 天前
总结:在工作场景中的应用。(Excel)
excel
GitCode官方2 天前
直播预告 | Excelize 跨语言实战
开源·excel·gitcode
listhi5202 天前
C# 操作 Excel
c#·excel·mfc
mysusheng2 天前
2025 批量下载雪球和东方财富帖子和文章导出excel和pdf
pdf·excel
mudtools2 天前
使用二次封装的Excel COM 组件操作Excel\WPS ET IExcelRange 高级应用
.net·excel
尼恩久3 天前
Excel工作技巧
爬虫·python·excel
玩泥巴的3 天前
使用二次封装的Excel COM 组件操作Excel\WPS ET IExcelRange 高级应用
excel·二次开发·com互操作
SYWG03 天前
【DBCExcelConvent】CAN报文解析辅助工具之DBC与Excel互转
c语言·qt·excel·can·dbc