天天日报丨Spring Boot + EasyExcel导入导出,简直太好用了!
老项目主要采用的POI框架来进行Excel数据的导入和导出,但经常会出现OOM的情况,导致整个服务不可用。后续逐步转移到EasyExcel,简直不能太好用了。
EasyExcel是阿里巴巴开源插件之一,主要解决了poi框架使用复杂,sax解析模式不容易操作,数据量大起来容易OOM,解决了POI并发造成的报错。主要解决方式:通过解压文件的方式加载,一行一行地加载,并且抛弃样式字体等不重要的数据,降低内存的占用。
【资料图】
在之前专门写过一篇文章《EasyExcel太方便易用了,强烈推荐!》,介绍EasyExcel功能的基本使用。今天这篇文章,我们基于SpringBoot来实现一下EasyExcel的集成,更加方便大家在实践中的直接使用。
SpringBoot项目集成 依赖集成创建一个基础的SpringBoot项目,比如这里采用SpringBoot 2.7.2版本。
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test
EasyExcel在SpringBoot的集成非常方便,只需引入对应的pom依赖即可。在上述dependencies中添加EasyExcel的依赖:
com.alibaba easyexcel 2.2.11
EasyExcel目前稳定最新版本2.2.11。如果想查看开源项目或最新版本,可在GitHub上获得:https://github.com/alibaba/easyexcel。
为了方便和简化代码编写,这里同时引入了Lombok的依赖,后续代码中也会使用对应的注解。
org.projectlombok lombok 1.18.22
下面正式开始业务相关代码的编写。如果你想直接获得完整源码,对照源码阅读本篇文章,可在公号「程序新视界」内回“1007”获得完整源码。
实体类实现这里创建一个Member,会员的实体类,并在实体类中填写基础的个人信息。
@DatapublicclassMember{/*** EasyExcel使用:导出时忽略该字段*/@ExcelIgnoreprivateIntegerid;@ExcelProperty("用户名")@ColumnWidth(20)privateStringusername;/*** EasyExcel使用:日期的格式化*/@ColumnWidth(20)@ExcelProperty("出生日期")@DateTimeFormat("yyyy-MM-dd")privateDatebirthday;/*** EasyExcel使用:自定义转换器*/@ColumnWidth(10)@ExcelProperty(value="性别",converter=GenderConverter.class)privateIntegergender;}
为了尽量多的演示EasyExcel的相关功能,在上述实体类中使用了其常见的一些注解:
@ExcelIgnore:忽略掉该字段;@ExcelProperty("用户名"):设置该列的名称为”用户名“;@ColumnWidth(20):设置表格列的宽度为20;@DateTimeFormat("yyyy-MM-dd"):按照指定的格式对日期进行格式化;@ExcelProperty(value = "性别", converter = GenderConverter.class):自定义内容转换器,类似枚举的实现,将“男”、“女”转换成“0”、“1”的数值。GenderConverter转换器的代码实现如下:
publicclassGenderConverterimplementsConverter{privatestaticfinalStringMAN="男";privatestaticfinalStringWOMAN="女";@OverridepublicClass>supportJavaTypeKey(){//实体类中对象属性类型returnInteger.class;}@OverridepublicCellDataTypeEnumsupportExcelTypeKey(){//Excel中对应的CellData属性类型returnCellDataTypeEnum.STRING;}@OverridepublicIntegerconvertToJavaData(CellDatacellData,ExcelContentPropertyexcelContentProperty,GlobalConfigurationglobalConfiguration){//从Cell中读取数据Stringgender=cellData.getStringValue();//判断Excel中的值,将其转换为预期的数值if(MAN.equals(gender)){return0;}elseif(WOMAN.equals(gender)){return1;}returnnull;}@OverridepublicCellData>convertToExcelData(Integerinteger,ExcelContentPropertyexcelContentProperty,GlobalConfigurationglobalConfiguration){//判断实体类中获取的值,转换为Excel预期的值,并封装为CellData对象if(integer==null){returnnewCellData<>("");}elseif(integer==0){returnnewCellData<>(MAN);}elseif(integer==1){returnnewCellData<>(WOMAN);}returnnewCellData<>("");}}
不同版本中,convertToJavaData和convertToExcelData的方法参数有所不同,对应的值的获取方式也不同,大家在使用时注意对照自己的版本即可。
业务逻辑实现为方便验证功能,DAO层的逻辑便不再实现,直接通过Service层来封装数据,先来看导出功能的业务类实现。
MemberService实现定义MemberService接口:
publicinterfaceMemberService{/***获取所有的成员信息*@return成员信息列表*/ListgetAllMember();}
定义MemberServiceImpl实现类:
@Service("memberService")publicclassMemberServiceImplimplementsMemberService{@OverridepublicListgetAllMember(){//这里构造一些测试数据,具体业务场景可从数据库等其他地方获取List list=newArrayList<>();Membermember=newMember();member.setUsername("张三");member.setBirthday(getDate(1990,10,11));member.setGender(0);list.add(member);Membermember1=newMember();member1.setUsername("王红");member1.setBirthday(getDate(1999,3,29));member1.setGender(1);list.add(member1);Membermember2=newMember();member2.setUsername("李四");member2.setBirthday(getDate(2000,2,9));member2.setGender(0);list.add(member2);returnlist;}privateDategetDate(intyear,intmonth,intday){Calendarcalendar=Calendar.getInstance();calendar.set(year,month,day);returncalendar.getTime();}}
其中数据采用模拟的静态数据,返回Member列表。
简单导出实现在Controller层的实现一个简单的导出实现:
/***普通导出方式*/@RequestMapping("/export1")publicvoidexportMembers1(HttpServletResponseresponse)throwsIOException{Listmembers=memberService.getAllMember();//设置文本内省response.setContentType("application/vnd.ms-excel");//设置字符编码response.setCharacterEncoding("utf-8");//设置响应头response.setHeader("Content-disposition","attachment;filename=demo.xlsx");EasyExcel.write(response.getOutputStream(),Member.class).sheet("成员列表").doWrite(members);}
这个实现方式非常简单直接,使用EasyExcel的write方法将查询到的数据进行处理,以流的形式写出即可。
在浏览器访问对应的链接,可下载到如下Excel内容:
如果我们需要将导出的Excel进行一些格式化的处理,这就需要用到导出策略的实现了。
自定义导入实现在EasyExcel执行write方法之后,获得ExcelWriterBuilder类,通过该类的registerWriteHandler方法可以设置一些处理策略。
这里先实现一个通用的格式策略工具类CommonCellStyleStrategy:
publicclassCommonCellStyleStrategy{/***设置单元格样式(仅用于示例)**@return样式策略*/publicstaticHorizontalCellStyleStrategygetHorizontalCellStyleStrategy(){//表头策略WriteCellStyleheaderCellStyle=newWriteCellStyle();//表头水平对齐居中headerCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//背景色headerCellStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());WriteFontheaderFont=newWriteFont();headerFont.setFontHeightInPoints((short)15);headerCellStyle.setWriteFont(headerFont);//自动换行headerCellStyle.setWrapped(Boolean.FALSE);//内容策略WriteCellStylecontentCellStyle=newWriteCellStyle();//设置数据允许的数据格式,这里49代表所有可以都允许设置contentCellStyle.setDataFormat((short)49);//设置背景色:需要指定FillPatternType为FillPatternType.SOLID_FOREGROUND不然无法显示背景颜色.头默认了FillPatternType所以可以不指定contentCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);contentCellStyle.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());//设置内容靠左对齐contentCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);//设置字体WriteFontcontentFont=newWriteFont();contentFont.setFontHeightInPoints((short)12);contentCellStyle.setWriteFont(contentFont);//设置自动换行contentCellStyle.setWrapped(Boolean.FALSE);//设置边框样式和颜色contentCellStyle.setBorderLeft(BorderStyle.MEDIUM);contentCellStyle.setBorderTop(BorderStyle.MEDIUM);contentCellStyle.setBorderRight(BorderStyle.MEDIUM);contentCellStyle.setBorderBottom(BorderStyle.MEDIUM);contentCellStyle.setTopBorderColor(IndexedColors.RED.getIndex());contentCellStyle.setBottomBorderColor(IndexedColors.GREEN.getIndex());contentCellStyle.setLeftBorderColor(IndexedColors.YELLOW.getIndex());contentCellStyle.setRightBorderColor(IndexedColors.ORANGE.getIndex());//将格式加入单元格样式策略returnnewHorizontalCellStyleStrategy(headerCellStyle,contentCellStyle);}}
该类中示例设置了Excel的基础格式。
再来实现一个精细化控制单元格内容CellWriteHandler的实现类:
/***实现CellWriteHandler接口,实现对单元格样式的精确控制**@authorsec*@version1.0*@date2022/7/31**/publicclassCustomCellWriteHandlerimplementsCellWriteHandler{/***创建单元格之前的操作*/@OverridepublicvoidbeforeCellCreate(WriteSheetHolderwriteSheetHolder,WriteTableHolderwriteTableHolder,Rowrow,Headhead,Integerinteger,Integerinteger1,BooleanaBoolean){}/***创建单元格之后的操作*/@OverridepublicvoidafterCellCreate(WriteSheetHolderwriteSheetHolder,WriteTableHolderwriteTableHolder,Cellcell,Headhead,Integerinteger,BooleanaBoolean){}/***单元格内容转换之后的操作*/@OverridepublicvoidafterCellDataConverted(WriteSheetHolderwriteSheetHolder,WriteTableHolderwriteTableHolder,CellDatacellData,Cellcell,Headhead,Integerinteger,BooleanaBoolean){}/***单元格处理后(已写入值)的操作*/@OverridepublicvoidafterCellDispose(WriteSheetHolderwriteSheetHolder,WriteTableHolderwriteTableHolder,Listlist,Cellcell,Headhead,Integerinteger,BooleanisHead){//设置超链接if(isHead&&cell.getRowIndex()==0&&cell.getColumnIndex()==0){CreationHelperhelper=writeSheetHolder.getSheet().getWorkbook().getCreationHelper();Hyperlinkhyperlink=helper.createHyperlink(HyperlinkType.URL);hyperlink.setAddress("https://github.com/alibaba/easyexcel");cell.setHyperlink(hyperlink);}//精确设置单元格格式booleanbool=isHead&&cell.getRowIndex()==1;if(bool){//获取工作簿Workbookworkbook=writeSheetHolder.getSheet().getWorkbook();CellStylecellStyle=workbook.createCellStyle();FontcellFont=workbook.createFont();cellFont.setBold(Boolean.TRUE);cellFont.setFontHeightInPoints((short)14);cellFont.setColor(IndexedColors.SEA_GREEN.getIndex());cellStyle.setFont(cellFont);cell.setCellStyle(cellStyle);}}}
在这里,对单元格表头的第0个Cell设置了一个超链接。
通过上面的定义两个策略实现,在导出Excel可以使用上述两个策略实现:
/***基于策略及拦截器导出*/@RequestMapping("/export2")publicvoidexportMembers2(HttpServletResponseresponse)throwsIOException{Listmembers=memberService.getAllMember();//设置文本内省response.setContentType("application/vnd.ms-excel");//设置字符编码response.setCharacterEncoding("utf-8");//设置响应头response.setHeader("Content-disposition","attachment;filename=demo.xlsx");EasyExcel.write(response.getOutputStream(),Member.class).sheet("成员列表")//注册通用格式策略.registerWriteHandler(CommonCellStyleStrategy.getHorizontalCellStyleStrategy())//设置自定义格式策略.registerWriteHandler(newCustomCellWriteHandler()).doWrite(members);}
通过浏览器,访问上述接口,导出的Excel格式如下:
可以看出,导出的Excel已经附带了具体的格式。其中表头“用户名”上也携带了对应的超链接。其他更精细化的控制,大家可以在策略类中做进一步的控制。
同步获取结果导入实现所谓的同步获取结果导入,就是执行导入操作时,将导入内容解析封装成一个结果列表返回给业务,业务代码再对列表中的数据进行集中的处理。
先来看同步导入的实现方式。
/***从Excel导入会员列表*/@RequestMapping(value="/import1",method=RequestMethod.POST)@ResponseBodypublicvoidimportMemberList(@RequestPart("file")MultipartFilefile)throwsIOException{Listlist=EasyExcel.read(file.getInputStream()).head(Member.class).sheet().doReadSync();for(Membermember:list){System.out.println(member);}}
注意,在上述代码中,最终调用的是doReadSync()方法。
这里直接用PostMan进行相应的文件上传请求:
执行导入请求,会发现控制台打印出对应的解析对象:
Member(id=null,username=张三,birthday=SunNov1100:00:00CST1990,gender=0)Member(id=null,username=王红,birthday=ThuApr2900:00:00CST1999,gender=1)Member(id=null,username=李四,birthday=ThuMar0900:00:00CST2000,gender=0)
说明上传成功,并且解析成功。
基于监听导入实现上面示例中是基于同步获取结果列表的形式进行导入,还有一种实现方式是基于监听器的形式来实现。这种形式可以达到边解析边处理业务逻辑的效果。
定义Listener:
publicclassMemberExcelListenerextendsAnalysisEventListener{@Overridepublicvoidinvoke(Membermember,AnalysisContextanalysisContext){//dosomethingSystem.out.println("读取Member="+member);//dosomething}@OverridepublicvoiddoAfterAllAnalysed(AnalysisContextanalysisContext){//dosomethingSystem.out.println("读取Excel完毕");//dosomething}}
在MemberExcelListener中可以针对每条数据进行对应的业务逻辑处理。
对外接口实现如下:
/***基于Listener方式从Excel导入会员列表*/@RequestMapping(value="/import2",method=RequestMethod.POST)@ResponseBodypublicvoidimportMemberList2(@RequestPart("file")MultipartFilefile)throwsIOException{//方式一:同步读取,将解析结果返回,比如返回List,业务再进行相应的数据集中处理//方式二:对照doReadSync()方法的是最后调用doRead()方法,不进行结果返回,而是在MemberExcelListener中进行一条条数据的处理;//此处示例为方式二EasyExcel.read(file.getInputStream(),Member.class,newMemberExcelListener()).sheet().doRead();}
这里采用了doRead()方法进行读取操作。在PostMan中再次上传Excel,打印日志如下:
读取Member=Member(id=null,username=张三,birthday=SunNov1100:00:00CST1990,gender=0)读取Member=Member(id=null,username=王红,birthday=ThuApr2900:00:00CST1999,gender=1)读取Member=Member(id=null,username=李四,birthday=ThuMar0900:00:00CST2000,gender=0)读取Excel完毕
说明解析成功,并且在解析的过程中,进行了业务逻辑的处理。
小结本篇文章基于SpringBoot集成EasyExcel的实现展开,为大家讲解了EasyExcel在实践中的具体运用。大家可根据需要,进行变通处理。同时,基于自定义转换器、自定义策略、自定义监听器等形式达到灵活适用于各种场景。希望本篇文章能给大家带来帮助。
代码真的越改越烂。。
2022-07-31
Redis中的布隆过滤器与布谷鸟过滤器,你了解多少?
2022-07-30
为了实现CI/CD,先来定制一个Docker镜像【实战精华篇】
2022-07-29
Linux安装Docker完整教程
2022-07-28
CAS+失败重试方式实现数据库的原子性更新
2022-07-27
面试被问Linux 命令su和sudo的区别?
2022-07-26
如果你觉得这篇文章不错,那么,下篇通常会更好。备注“公众号”添加微信好友(微信号:zhuan2quan)。
▲长按关注”程序新视界“,洞察技术内幕相关阅读
-
世界热推荐:今晚7:00直播丨下一个突破...
今晚19:00,Cocos视频号直播马上点击【预约】啦↓↓↓在运营了三年... -
NFT周刊|Magic Eden宣布支持Polygon网...
Block-986在NFT这样的市场,每周都会有相当多项目起起伏伏。在过去... -
环球今亮点!头条观察 | DeFi的兴衰与...
在比特币得到机构关注之后,许多财务专家预测世界将因为加密货币的... -
重新审视合作,体育Crypto的可靠关系才能双赢
Block-987即使在体育Crypto领域,人们的目光仍然集中在FTX上。随着... -
简讯:前端单元测试,更进一步
前端测试@2022如果从2014年Jest的第一个版本发布开始计算,前端开发... -
焦点热讯:刘强东这波操作秀
近日,刘强东发布京东全员信,信中提到:自2023年1月1日起,逐步为...