项目中经常会有列表查询,然后导出excel的功能,以下是其中一种方法,简单写个Demo
,先看项目结构:
- pom.xml
<properties><spring-boot.version>2.3.12.RELEASE</spring-boot.version></properties><dependencies><!--注意:由于 spring-boot-starter-web 默认替我们引入了核心启动器 spring-boot-starter,
因此,当 Spring Boot 项目中的 pom.xml 引入了 spring-boot-starter-web 的依赖后,
就无须在引入 spring-boot-starter 核心启动器的依赖了--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>${spring-boot.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>${spring-boot.version}</version><scope>test</scope></dependency><!--整合mytais--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.3</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.20</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.16</version><scope>compile</scope></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.3</version></dependency><!--excel--><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.2</version></dependency><!--excel2003版本要引入此包--><!-- <dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>--></dependencies>
其他依赖就不提了,主要就只这个依赖org.apache.poi.poi-ooxml
说明:
poi是Apache旗下的一个开源项目,由Apache官方维护,poi有两个不同的jar包,分别是处理excel2003和excel2007+的,对应的是poi和poi-ooxml。毕竟poi-ooxml是poi的升级版本,处理的单页数据量也是百万级别的,所以我们选择的也是poi-ooxml。
- application.yml
spring:datasource:driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.233.136:3306/mydata?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf8&allowMultiQueries=trueusername: root
password:123456mybatis:mapperLocations: classpath*:mapper/*Mapper.xml# 打印sql语句logging:level:com.meng.user: debug
- Excel2Application
@SpringBootApplication@MapperScan("com.meng.dao")publicclassExcel2Application{publicstaticvoidmain(String[] args){SpringApplication.run(Excel2Application.class, args);}}
- entity
@DatapublicclassBaiDuResult{privateLong id;/**
*标题
*/privateString title;/**
*内容
*/privateString content;/**
*内容来源
*/privateString sourceUrl;/**
*封面图片
*/privateString imgUrl;/**
*创建时间
*/privateDate createTime;/**
*更新时间
*/privateDate updateTime;/**
*是否删除
*/privateByte delFlag;}
- dao
@RepositorypublicinterfaceBaiDuResultDao{List<BaiDuResult>findAllPage(@Param("start")int start ,@Param("pageSize")int pageSize);
- BaiDuResultMapper.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.meng.dao.BaiDuResultDao"><resultMapid="BaseResultMap"type="com.meng.entity.BaiDuResult"><idcolumn="id"property="id"jdbcType="BIGINT"/><resultcolumn="title"property="title"jdbcType="VARCHAR"/><resultcolumn="content"property="content"jdbcType="VARCHAR"/><resultcolumn="source_url"property="sourceUrl"jdbcType="VARCHAR"/><resultcolumn="img_url"property="imgUrl"jdbcType="VARCHAR"/><resultcolumn="create_time"property="createTime"jdbcType="TIMESTAMP"/><resultcolumn="update_time"property="updateTime"jdbcType="TIMESTAMP"/><resultcolumn="del_flag"property="delFlag"jdbcType="TINYINT"/></resultMap><sqlid="Base_Column_List">
id, title, content, source_url, img_url, create_time, update_time, del_flag
</sql><selectid="findAllPage"resultMap="BaseResultMap">
select
<includerefid="Base_Column_List"/>
from result limit #{start} , #{pageSize}
</select>
- service
@ServicepublicclassResultService{@AutowiredprivateBaiDuResultDao dao;publicWorkbookexportToExcel(){//这是表头String[] arr ={"ID","标题","内容","内容来源","封面图片","创建时间","更新时间","是否删除"};//这是具体数据List<BaiDuResult> list = dao.findAllPage(0,1000);Workbook workbook =ExcelUtil.writeToExcelByList(arr, list ,BaiDuResult.class);return workbook;}}
- controller
@RestControllerpublicclassResultController{@AutowiredprivateResultService resultService;@GetMapping("/export")publicvoidexportResult(HttpServletResponse response)throwsIOException{Workbook wb = resultService.exportToExcel();OutputStream output = response.getOutputStream();String fileName ="结果表.xlsx";try{
fileName =URLEncoder.encode(fileName,"UTF-8");}catch(UnsupportedEncodingException e){
e.printStackTrace();}
response.setHeader("Content-disposition","attachment;filename="+ fileName +";"+"filename*=utf-8''"+ fileName);
wb.write(output);
output.close();}}
- ExcelUtils 以上都不重要,重要的是这个工具类的方法
publicclassExcelUtil{publicstatic<T>WorkbookwriteToExcelByList(String[] array,List<T> list ,Class<T> clazz){//创建工作薄Workbook wb =newXSSFWorkbook();//标题和页码CellStyle titleStyle = wb.createCellStyle();// 设置单元格对齐方式,水平居左
titleStyle.setAlignment(HorizontalAlignment.LEFT);//单元格边框
titleStyle.setBorderTop(BorderStyle.THIN);
titleStyle.setBorderLeft(BorderStyle.THIN);
titleStyle.setBorderRight(BorderStyle.THIN);
titleStyle.setBorderBottom(BorderStyle.THIN);// 设置字体样式Font titleFont = wb.createFont();// 字体高度
titleFont.setFontHeightInPoints((short)12);// 字体样式
titleFont.setFontName("黑体");
titleStyle.setFont(titleFont);//创建sheetSheet sheet = wb.createSheet("接入详情");// 自动设置宽度
sheet.autoSizeColumn(0);// 在sheet中添加标题行// 行数从0开始Row row = sheet.createRow(0);for(int i =0; i < array.length; i++){Cell cell = row.createCell(i);
cell.setCellValue(array[i]);
cell.setCellStyle(titleStyle);}// 数据样式 因为标题和数据样式不同 需要分开设置 不然会覆盖CellStyle dataStyle = wb.createCellStyle();//单元格边框
dataStyle.setBorderTop(BorderStyle.THIN);
dataStyle.setBorderLeft(BorderStyle.THIN);
dataStyle.setBorderRight(BorderStyle.THIN);
dataStyle.setBorderBottom(BorderStyle.THIN);// 设置居中样式,水平居中
dataStyle.setAlignment(HorizontalAlignment.CENTER);//数据从序号1开始try{int index =1;Field[] fields = clazz.getDeclaredFields();for(T t : list){// 默认的行数从0开始,为了统一格式设置从1开始,就是从excel的第二行开始
row = sheet.createRow(index);for(int i =0; i < fields.length; i++){// 默认的行数从0开始,为了统一格式设置从1开始,就是从excel的第二行开始Cell cell = row.createCell(i);// 为当前列赋值Field field = fields[i];if(!field.isAccessible()){
field.setAccessible(true);}Object value =(newPropertyDescriptor(field.getName(), clazz)).getReadMethod().invoke(t);setValue(cell , value);//设置数据的样式
cell.setCellStyle(dataStyle);}
index++;}}catch(Exception e){
e.printStackTrace();}return wb;}/**
* 设置cellValue
* 这里可以根据value的类型,进行格式化,比如日期格式化
*/privatestaticvoidsetValue(Cell cell ,Object value){DateFormat dateFormat =newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");if(value !=null&&!"".equals(value)){if(value instanceofDate){
cell.setCellValue(dateFormat.format(value));}else{
cell.setCellValue(value.toString());}}}}
这个writeToExcelByList,是利用反射,获取class的fields,然后循环遍历插入到cell,这里可以自定义Annotation,然后进行一些自定义的操作,比如日期格式化、该field是否导出到excel中等,这里只做简单的演示
- 导出结果 这里的单元格宽度应该是可以设置的,但我就不研究了,有需要再说
本文是采用Apache的开源项目poi来实现的,这个在企业中应用较多(个人观点),还有一个EasyExcel项目,是alibaba出的,看起来也不错,但我就不尝试了,放个链接 简洁、快速、节约内存的Excel处理工具EasyExcel ,或者直接去官网看文档吧,更清楚 EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel
本文参考:Spring boot实现Excel导出文件
大数据量的处理
首先介绍一下目前导出excel的几种格式:Excel 2003、Excel 2007
Excel 2003:在POI中使用HSSF对象时,excel 2003最多只允许存储65536条数据,一般用来处理较少的数据量。这时对于百万级别数据,Excel肯定容纳不了。
Excel 2007:当POI升级到XSSF对象时,它可以直接支持excel2007以上版本,因为它采用ooxml格式。这时excel可以支持1048576条数据,单个sheet表就支持近104万条数据了,虽然这时导出100万数据能满足要求,但使用XSSF测试后发现偶尔还是会发生堆溢出,所以也不适合百万数据的导出。
在POI3.8之后新增加了一个类,SXSSFWorkbook,采用当数据加工时不是类似前面版本的对象,
它可以控制excel数据占用的内存,他通过控制在内存中的行数来实现资源管理,即当创建对象超过了设定的行数,
它会自动刷新内存,将数据写入文件,这样导致打印时,占用的CPU,和内存很少。
所以可以使用SXXFWorkBook来实现百万级别数据量的导出。
只要将上文中的Workbook,替换成SXSSFWorkbook即可
这里参考: Java 使用POI 导出 百万级别的数据量的 Excel
大数据量的处理二
其实上文的大数据量处理方式有个问题,比如我要从数据库查询100万的数据,然后导出到excel,这个查询的过程(包括数据处理的过程)是很漫长的,等处理完,页面才会弹出下载弹窗,可能要等待十几秒甚至几十秒,不知情的用户还以为是系统异常了,也就是说不能快速响应,至少我还没找到快速响应的方式
那么还有另一种方式解决这个问题,就是使用csv文件替代表格(csv可以和表格互相转换且使用效果基本相同)。csv格式文件和txt文件一样理论上没有大小上限,这种方式就不需要poi的依赖了。
直接上代码:
entity加上toString方法:
@OverridepublicStringtoString(){return id +","+ title +","+ content +","+ sourceUrl +","+ imgUrl +","+ createTime +","+ updateTime +","+ delFlag;}
@RestControllerpublicclassResultController{@AutowiredprivateBaiDuResultDao dao;@GetMapping("/export")publicvoidexportResult(HttpServletResponse response){try{
response.reset();
response.setContentType("application/csv;charset=GBK");
response.setHeader("Content-Disposition","attachment;filename=file"+System.currentTimeMillis()+".csv");
response.setCharacterEncoding("GBK");PrintWriter out = response.getWriter();
out.println("id,标题,内容,内容来源,封面图片,创建时间,更新时间,是否删除");int limit =100000;int pageSize =10000;for(int i =0; i < limit; i++){List<BaiDuResult> list = dao.findAllPage(i, pageSize);for(BaiDuResult baiDuResult : list){String str = baiDuResult.toString();
out.println(str);}
i += pageSize;}
out.flush();
out.close();}catch(Exception e){
e.printStackTrace();}}}
这里可以看到,这里可以用分页的方式,将每次分页结果直接响应给页面,也就是用户点击下载,立马弹窗,然后下载的过程就耗时较长了,这样用户的交互会好一些。
但是!!!!!!csv文件会比excel文件大很多很多,完全不在一个数量级,因为csv可以用记事本打开,本质上是字符串文件,没有任何压缩,xls之类的都是有压缩的,需要专门软件打开。
参考:java实现大数据Excel导出
版权归原作者 jdbcmeng 所有, 如有侵权,请联系我们删除。