项目中经常会有列表查询,然后导出excel的功能,以下是其中一种方法,简单写个Demo ,先看项目结构:
- pom.xml
<properties>
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
</properties>
<dependencies>
<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>
<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>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</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=true
username: root
password: 123456
mybatis:
mapperLocations: classpath*:mapper/*Mapper.xml
logging:
level:
com.meng.user: debug
- Excel2Application
@SpringBootApplication
@MapperScan("com.meng.dao")
public class Excel2Application {
public static void main(String[] args) {
SpringApplication.run(Excel2Application.class , args);
}
}
- entity
@Data
public class BaiDuResult {
private Long id;
private String title;
private String content;
private String sourceUrl;
private String imgUrl;
private Date createTime;
private Date updateTime;
private Byte delFlag;
}
- dao
@Repository
public interface BaiDuResultDao {
List<BaiDuResult> findAllPage(@Param("start") int start , @Param("pageSize")int pageSize);
- BaiDuResultMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.meng.dao.BaiDuResultDao" >
<resultMap id="BaseResultMap" type="com.meng.entity.BaiDuResult" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="title" property="title" jdbcType="VARCHAR" />
<result column="content" property="content" jdbcType="VARCHAR" />
<result column="source_url" property="sourceUrl" jdbcType="VARCHAR" />
<result column="img_url" property="imgUrl" jdbcType="VARCHAR" />
<result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP" />
<result column="del_flag" property="delFlag" jdbcType="TINYINT" />
</resultMap>
<sql id="Base_Column_List" >
id, title, content, source_url, img_url, create_time, update_time, del_flag
</sql>
<select id="findAllPage" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from result limit #{start} , #{pageSize}
</select>
- service
@Service
public class ResultService {
@Autowired
private BaiDuResultDao dao;
public Workbook exportToExcel(){
String[] arr = {"ID","标题","内容","内容来源","封面图片","创建时间","更新时间","是否删除"};
List<BaiDuResult> list = dao.findAllPage(0, 1000);
Workbook workbook = ExcelUtil.writeToExcelByList(arr, list , BaiDuResult.class);
return workbook;
}
}
- controller
@RestController
public class ResultController {
@Autowired
private ResultService resultService;
@GetMapping("/export")
public void exportResult(HttpServletResponse response) throws IOException {
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
以上都不重要,重要的是这个工具类的方法
public class ExcelUtil{
public static <T> Workbook writeToExcelByList(String[] array, List<T> list , Class<T> clazz) {
Workbook wb = new XSSFWorkbook();
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);
Sheet sheet = wb.createSheet("接入详情");
sheet.autoSizeColumn(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);
try {
int index = 1;
Field[] fields = clazz.getDeclaredFields();
for (T t : list) {
row = sheet.createRow(index);
for (int i = 0; i < fields.length; i++) {
Cell cell = row.createCell(i);
Field field = fields[i];
if (!field.isAccessible()) {
field.setAccessible(true);
}
Object value = (new PropertyDescriptor(field.getName(), clazz)).getReadMethod().invoke(t);
setValue(cell , value);
cell.setCellStyle(dataStyle);
}
index++;
}
} catch (Exception e) {
e.printStackTrace();
}
return wb;
}
private static void setValue(Cell cell , Object value){
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if(value != null && !"".equals(value)){
if(value instanceof Date){
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方法:
@Override
public String toString() {
return id +
"," + title +
"," + content +
"," + sourceUrl +
"," + imgUrl +
"," + createTime +
"," + updateTime +
"," + delFlag;
}
@RestController
public class ResultController {
@Autowired
private BaiDuResultDao dao;
@GetMapping("/export")
public void exportResult(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导出
|