摘要:poi:功能强大,使用起来相对复杂,大数据时可能导致内存溢出easypoi:基于poi实现,功能强大,简单易用,大数据时可能导致内存溢出,小数据量时推荐easyexcel:基于poi实现,性能更优,大数据量时推荐本文只介绍easyexcel,并介绍常见问题比如
项目开发过程中,免不了需要数据导出功能,常见的导出工具包有poi,easypoi,easyexcel,它们各有优缺点,简要来说:
poi:功能强大,使用起来相对复杂,大数据时可能导致内存溢出easypoi:基于poi实现,功能强大,简单易用,大数据时可能导致内存溢出,小数据量时推荐easyexcel:基于poi实现,性能更优,大数据量时推荐本文只介绍easyexcel,并介绍常见问题比如字典、样式的解决方案介绍数据导出业务流程之前,先梳理下数据导出,什么是数据导出,为什么需要数据导出?
我们都知道数据存储在数据库中,数据库中的数据可以通过前端页面或者APP呈现给使用者,其实通过excel将数据导出也是一种呈现方式,而且可以通过excel对数据做更复杂的处理。
前面说了,excel导出和前端页面、APP一样都是一种呈现方式,所以前端页面和APP在将数据呈现给用户的过程中遇到的问题,像属性转换(数据库存储的name,要呈现给使用者名称)、字典转换(数据库存储的1/0,要呈现给使用者启用/停用)等等问题,excel导出一样会遇到。下面介绍下数据呈现必须要经过业务流程
获取需要导出的数据集合数据属性和自然语言映射关系,将数据对象的属性转为使用者可以理解的自然语言数据字典值和自然语言映射关系,将属性的字典值转为使用者可以理解的自然语言(非字典值,是什么值就呈现什么值)数据样式和自然语言样式映射关系,将数据样式转为使用者可以理解的自然语言样式设置表格样式将数据集合按照上述映射关系和表格样式,写入到excel中用户下载excelcom.alibabaeasyexcel4.0.3com.alibabafastjson1.2.46org.projectlomboklombok1.18.0package com.yu.demo.tools;import com.alibaba.fastjson.JSON;import com.fasterxml.jackson.core.type.TypeReference;import java.lang.reflect.Type;import java.util.Map;/*** JSON工具类** @author admin*/public abstract class JsonUtil {private JsonUtil {}public final static Type MAP_INTEGER_String = new TypeReference> {}.getType;/*** json串转Map(Map的value类型一致时使用)** @param jsonString json串* @return 对象*/public static Map json2Map(String jsonString, Type type) {return JSON.parseObject(jsonString, type);}}package com.yu.demo.tools;import java.lang.annotation.*;@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface DictSource {/*** 字典类型主键*/String dictTypeId default "";/*** 字典内容json串*/String dictContentJson default "";}package com.yu.demo.tools;import com.alibaba.excel.converters.Converter;import com.alibaba.excel.enums.CellDataTypeEnum;import com.alibaba.excel.metadata.GlobalConfiguration;import com.alibaba.excel.metadata.data.WriteCellData;import com.alibaba.excel.metadata.property.ExcelContentProperty;import org.apache.poi.util.StringUtil;import java.lang.reflect.Field;import java.util.Map;public class IntegerDictConverter implements Converter {@Overridepublic CellDataTypeEnum supportExcelTypeKey {return CellDataTypeEnum.STRING;}@Overridepublic WriteCellData convertToExcelData(Integer value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {//属性值为空时,直接返回if (value == null) {//为空时的处理,与前端展示保持一致即可return new WriteCellData("");}//获取添加@ExcelProperty注解且converter = IntegerDictConverter.class的属性Field field = contentProperty.getField;//获取该属性的DictConverter注解信息DictSource dictSource = field.getannotation(DictSource.class);//配置了converter = IntegerDictConverter.class的属性,但是没有添加DictSource注解的直接返回if (dictSource == null) {return new WriteCellData(String.valueOf(value));}//获取配置的dictTypeIdString dictTypeId = dictSource.dictTypeId;//获取配置的dictContentJsonString dictContentJson = dictSource.dictContentJson;//判断dictTypeId是否为空boolean nullDictType = StringUtil.isBlank(dictTypeId);//判断nullDictContentJson是否为空boolean nullDictContentJson = StringUtil.isBlank(dictContentJson);//字典配置都为空时,将属性值转为字符串直接返回if (nullDictType && nullDictContentJson) {return new WriteCellData(String.valueOf(value));}//优先使用dictTypeId处理转换if (!nullDictType) {//通过dictTypeId获取字典内容集合:List dictContents = dictContentService.listByDictTypeId(dictTypeId);//主键是数值的,将dictTypeId转为数值//遍历字典内容,匹配属性值与字典值:value.equals(dictContent.getValue)//匹配成功后获取字典名称返回:return new WriteCellData(dictContent.getName);//如果没有匹配成功使用dictContentJson处理转换}if (!nullDictContentJson) {Map dictContentMap = JsonUtil.json2Map(dictContentJson, JsonUtil.MAP_INTEGER_STRING);String cnName = dictContentMap.get(value);if (StringUtil.isNotBlank(cnName)) {return new WriteCellData(cnName);}}//没有转换成功时使用默认属性值return new WriteCellData(String.valueOf(value));}}package com.yu.demo.web.easyexcel.entity;import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;import com.alibaba.excel.annotation.ExcelProperty;import com.alibaba.excel.annotation.format.DateTimeFormat;import com.alibaba.excel.annotation.write.style.ColumnWidth;import com.alibaba.excel.converters.date.DateStringConverter;import com.yu.demo.web.easyexcel.component.DictSource;import com.yu.demo.web.easyexcel.component.IntegerDictConverter;import lombok.Getter;import lombok.Setter;import lombok.ToString;import java.util.Date;@Setter@Getter@ToString//类上添加@ExcelIgnoreUnannotated时,属性没有@ExcelProperty注解时不导出//类上未添加@ExcelIgnoreUnannotated,属性没有@ExcelProperty注解时也导出@ExcelIgnoreUnannotatedpublic class User {/*** 名称*/@ExcelProperty("名称")private String name;/*** 密码* 类添加@ExcelIgnoreUnannotated,属性未添加@ExcelProperty,不导出*/private String password;/*** 生日* 日期样式处理* 1.使用@DateTimeFormat设置导出样式* 2.使用DateStringConverter处理导出*/@DateTimeFormat("yyyy-MM-dd HH:mm:ss")@ExcelProperty(value = "生日", converter = DateStringConverter.class)private Date birthday;/*** 性别* 字典转换处理*/@ColumnWidth(7)//指定列宽度,优先级高于LongestMatchColumnWidthStyleStrategy@ExcelProperty(value = "性别", converter = IntegerDictConverter.class)@DictSource(dictContentJson = "{0:'女',1:'男',2:'保密'}")private Integer sex;}package com.yu.demo.tools;import lombok.Getter;import lombok.Setter;import lombok.ToString;import java.util.List;/*** excel导入导出数据对象*/@Setter@Getter@ToStringpublic class SheetEntity {/*** sheet页名称(导出参数)* 可以为空,为空时,单sheet页没有名称,多sheet页序号为名称*/private String sheetName;/*** 数据类型(导入导出参数)*/private Class head;/*** 数据(导出参数)*/private List data;}导出的数据有如下三种及其说明
通过全路径文件名导出,easyexcel通过全路径文件名创建文件,将数据写入文件,当路径不存在时报错,适合场景:一次导出,多次下载通过文件导出,将数据写入文件,当路径不存在报错,适合场景:一次导出,多次下载通过输出流导出,将数据写入输出流,适合场景:导出一次下载一次package com.yu.demo.tools;import com.alibaba.excel.EasyExcel;import com.alibaba.excel.ExcelWriter;import com.alibaba.excel.support.ExcelTypeEnum;import com.alibaba.excel.write.builder.ExcelWriterBuilder;import com.alibaba.excel.write.handler.WriteHandler;import com.alibaba.excel.write.metadata.WriteSheet;import com.alibaba.excel.write.metadata.style.WriteCellStyle;import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;import org.apache.poi.ss.usermodel.HorizontalAlignment;import org.apache.poi.ss.usermodel.VerticalAlignment;import org.apache.poi.util.StringUtil;import java.io.File;import java.io.OutputStream;import java.util.List;/*** excel导入导出工具类(easyExcel实现)* easyPoi:并发量和数据量都不大时推荐,定制化的导出支持非常的丰富* easyExcel:高并发、大数据量时推荐*/public abstract class ExcelUtil {// 设置居中对齐的样式private static final WriteCellStyle CONTENT_WRITE_CELL_STYLE;private static final WriteHandler HORIZONTAL_CELL_STYLE_STRATEGY;static {CONTENT_WRITE_CELL_STYLE = new WriteCellStyle;//水平居中CONTENT_WRITE_CELL_STYLE.setHorizontalAlignment(HorizontalAlignment.CENTER);//垂直居中CONTENT_WRITE_CELL_STYLE.setVerticalAlignment(VerticalAlignment.CENTER);HORIZONTAL_CELL_STYLE_STRATEGY = new HorizontalCellStyleStrategy(null, CONTENT_WRITE_CELL_STYLE);}private ExcelUtil {}/*** 使用EasyExcel导出** @param fullFileName 文件路径+文件名+后缀(文件已存在时覆盖)* @param sheetName sheet名称(为空时使用默认值0)* @param head 数据类型(为空时没有表头,只有数据)* @param exportData 需要导出的数据(为空时,没有数据)*/public static void exportByEasyExcel(String fullFileName, String sheetName, Class head, List exportData) {File targetFile = new File(fullFileName);// 判断文件父目录是否存在if (!targetFile.getParentFile.exists) {boolean mkdirResult = targetFile.getParentFile.mkdirs;if (!mkdirResult) {return;}}ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(targetFile, head);if (fullFileName.endsWith(ExcelTypeEnum.XLS.getValue)) {excelWriterBuilder.excelType(ExcelTypeEnum.XLS);} else if (fullFileName.endsWith(ExcelTypeEnum.CSV.getValue)) {excelWriterBuilder.excelType(ExcelTypeEnum.CSV);} else {excelWriterBuilder.excelType(ExcelTypeEnum.XLSX);}excelWriterBuilder//设置列按最大长度调整.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy)//设置水平垂直居中.registerWriteHandler(HORIZONTAL_CELL_STYLE_STRATEGY).sheet(sheetName).doWrite(exportData);}/*** 使用EasyExcel导出** @param outputStream 输出流* @param sheetName sheet名称(为空时使用默认值0)* @param head 数据类型(为空时没有表头,只有数据)* @param exportData 需要导出的数据(为空时,没有数据)*/public static void exportByEasyExcel(OutputStream outputStream, ExcelTypeEnum excelType, String sheetName, Class head, List exportData) {EasyExcel.write(outputStream, head).excelType(excelType)//设置列按最大长度调整,非线程安全,每次都需要new.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy)//设置水平垂直居中.registerWriteHandler(HORIZONTAL_CELL_STYLE_STRATEGY).sheet(sheetName).doWrite(exportData);}/*** 使用EasyExcel导出多sheet页数据** @param outputStream 输出流* @param sheetEntities 导出数据对象集合*/public static void exportByEasyExcel(OutputStream outputStream, ExcelTypeEnum excelType, List> sheetEntities) {ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(outputStream).excelType(excelType);writeSheets(excelWriterBuilder, sheetEntities);}private static void writeSheets(ExcelWriterBuilder excelWriterBuilder, List> sheetEntities) {excelWriterBuilder.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy).registerWriteHandler(HORIZONTAL_CELL_STYLE_STRATEGY);ExcelWriter excelWriter = excelWriterBuilder.build;for (int i = 0; i sheetEntity = sheetEntities.get(i);Class head = sheetEntity.getHead;List exportData = sheetEntity.getData;String sheetName = StringUtil.isBlank(sheetEntity.getSheetName) ? String.valueOf(i + 1) : sheetEntity.getSheetName;WriteSheet writeSheet = EasyExcel.writerSheet(i + 1, sheetName).head(head).build;excelWriter.write(exportData, writeSheet);}excelWriter.finish;}}8、创建测试类package com.yu.demo.web.easyexcel.web;import com.alibaba.excel.support.ExcelTypeEnum;import com.yu.demo.web.easyexcel.entity.SheetEntity;import com.yu.demo.web.easyexcel.entity.User;import com.yu.demo.web.easyexcel.util.ExcelUtil;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.HttpHeaders;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.PostConstruct;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.net.URLEncoder;import java.nio.charset.StandardCharsets;import java.util.ArrayList;import java.util.Date;import java.util.List;@RestController@RequestMapping("user")public class UserController {@Value("${download.path}")private String filePath;private List users;private List> sheetEntities;@PostConstructpublic void init {users = new ArrayList(5);for (int i = 0; i (2);for (int i = 0; i sheetEntity = new SheetEntity;sheetEntity.setSheetName(i + "号sheet");sheetEntity.setHead(User.class);sheetEntity.setData(users);sheetEntities.add(sheetEntity);}}/*** 单sheet页通过全路径文件名导出测试接口(也可以通过文件流导出)* 返回文件名,前端通过web路径+文件名下载文件*/@GetMapping("/filePath")public String filePath {String fileName = "用户.xlsx";String fullFileName = filePath + fileName;ExcelUtil.exportByEasyExcel(fullFileName, "用户", User.class, users);return fileName;}/*** 多sheet页通过文件流导出(也可以通过全路径文件名导出)*/@GetMapping("/download")public void download(HttpServletResponse response) throws IOException {String fileName = "用户";response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding(StandardCharsets.UTF_8.name);String encodeFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", " ");response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=*=utf-8''" + encodeFileName + ExcelTypeEnum.XLSX.getValue);ExcelUtil.exportByEasyExcel(response.getOutputStream, ExcelTypeEnum.XLSX, sheetEntities);}}download:#配置文件下载路径path: C:\Users\Administrator\Desktopspring:web:resources:static-locations:#注册文件下载路径- file:${download.path}#系统默认配置- classpath:/META-INF/resources/- classpath:/resources/- classpath:/static/- classpath:/public/四、接口测试1、启用项目访问下载文件地址:http://localhost:8080/用户.xlsx访问接口地址:http://localhost:8080/user/download来源:那你为何对我三笑留情
免责声明:本站系转载,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请在30日内与本站联系,我们将在第一时间删除内容!