最近需求中又有excel导出的功能,看到项目里面使用得导出代码写得乱七八糟。根本不想再次复用。本次决定重新写一个工具类,逐步优化,用于后期需求使用。
思路:
依赖
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>3.17version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>3.17version>
<scope>compilescope>
dependency>
自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Excel {
/**
* 导出表格 列名
*
* @return
*/
public String name() default "";
}
核心代码
public class ExcelUtil<T> {
/**
* 存储行首数据
*/
private Map<Object, List<String>> topRowCache = new HashMap(6);
/**
* 导出数据
*/
private List<T> data;
/**
* 导出实体
*/
private Class<T> entityClass;
/**
* 行首数据
*/
private List<String> topRow = new ArrayList<>();
/**
* 导出到Stream
*/
private FileOutputStream outputStream;
/**
* 导出到文件
*/
private File outFile;
/**
* 文件对象
*/
private SXSSFWorkbook workbook;
public ExcelUtil() {
}
public ExcelUtil(Class<T> entityClass) {
this.entityClass = entityClass;
}
/**
* 导出数据到指定文件
*
* @param outFile 导出文件
* @param data 导出的数据
*/
public void exportToFile(File outFile, List<T> data) {
this.outFile = outFile;
this.data = data;
this.init();
this.dataFill();
}
/**
* 导出数据到指定输出流
*
* @param outputStream 导出数据到输出流
* @param data 导出的数据
*/
public void exportToStream(FileOutputStream outputStream, List<T> data) {
this.outputStream = outputStream;
this.data = data;
this.init();
this.dataFill();
}
/**
* 初始化行首数据
*/
private void init() {
// 取缓存数据
if (topRowCache.get(this.entityClass) == null) {
// 根据对象字段所标注的注解 获取首行数据
Field[] declaredFields = this.entityClass.getDeclaredFields();
if (declaredFields != null) {
for (Field field : declaredFields) {
Excel excel = field.getAnnotation(Excel.class);
if (excel == null) {
continue;
}
String name = excel.name();
this.topRow.add(name);
}
topRowCache.put(this.entityClass, this.topRow);
}
} else {
this.topRow = topRowCache.get(this.entityClass);
}
// 初始化excel文件 以及 填充首行数据
SXSSFWorkbook workbook = new SXSSFWorkbook();
// 创建sheet
SXSSFSheet sheet = workbook.createSheet("sheet");
// 创建首行
SXSSFRow row = sheet.createRow(0);
// 填充首行数据
for (int i = 0, len = this.topRow.size(); i < len; i++) {
SXSSFCell cell = row.createCell(i);
cell.setCellValue(topRow.get(i));
// 设置列宽 此处可做优化
sheet.setColumnWidth(i, 3500);
}
this.workbook = workbook;
}
/**
* 数据填充
*/
private void dataFill() {
try {
if (this.data == null || this.data.size() < 1) {
return;
}
// 获取excel的第一个页(sheet)
SXSSFSheet sheet = this.workbook.getSheetAt(0);
for (int i = 0, len = this.data.size(); i < len; i++) {
SXSSFRow row = sheet.createRow(i + 1);
row.setHeight((short) 450);
T t = this.data.get(i);
Field[] declaredFields = t.getClass().getDeclaredFields();
if (declaredFields == null) {
continue;
}
// 单元格位置
int offset = 0;
// 填充单元格数据
for (Field field : declaredFields) {
Excel excel = field.getAnnotation(Excel.class);
// 没有注解的属性不导出
if (excel == null) {
continue;
}
// 设置私有属性可访问性
field.setAccessible(true);
// 获取属性值
Object obj = field.get(t);
SXSSFCell cell = row.createCell(offset);
// 填充
cell.setCellValue(obj.toString());
offset++;
}
}
if (this.outputStream == null) {
Assert.notNull(this.outFile, "EXCEL输出 文件路径为空!");
this.outputStream = new FileOutputStream(this.outFile);
}
// 输出
workbook.write(this.outputStream);
} catch (IOException | IllegalAccessException e) {
e.printStackTrace();
} finally {
try {
workbook.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
实体类
public class QydjYykhEntity {
private String id;
/**
* 企业主键
*/
@Excel(name = "企业主键")
private String pripid;
/**
* 公司名称
*/
@Excel(name = "公司名称")
private String entname;
/**
* 预约网点
*/
@Excel(name = "预约网点")
private String bankno;
/**
* 办理人电话
*/
@Excel(name = "办理人电话")
private String tel;
/**
* 办理人
*/
@Excel(name = "办理人")
private String name;
....省略 get set
}
持久化到磁盘 测试代码
@Test
public void testExportToFile() {
ExcelUtil<QydjYykhEntity> comDmEntityExcelUtil = new ExcelUtil(QydjYykhEntity.class);
List<QydjYykhEntity> data = new ArrayList<>();
QydjYykhEntity qydjYykhEntity = new QydjYykhEntity();
qydjYykhEntity.setEntname("测试公司");
qydjYykhEntity.setTel("18006408888");
qydjYykhEntity.setBankno("测试网点");
qydjYykhEntity.setName("张三");
qydjYykhEntity.setPripid("1");
qydjYykhEntity.setId("1");
data.add(qydjYykhEntity);
data.add(qydjYykhEntity);
data.add(qydjYykhEntity);
data.add(qydjYykhEntity);
comDmEntityExcelUtil.exportToFile(new File("E:\\workspace\\test.xlsx"), data);
}
持久化到输出流 测试代码
public void downExcelTemplate(HttpServletResponse response) {
ExcelExportUtil<QydjYykhEntity> comDmEntityExcelUtil = new ExcelExportUtil(QydjYykhEntity.class);
ServletOutputStream outputStream = null;
response.setHeader("Content-Disposition", "attachment; filename=template.xlsx");
try {
outputStream = response.getOutputStream();
comDmEntityExcelUtil.exportToStream(outputStream, null);
outputStream.println();
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
try {
outputStream.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
PS:
扩展:
目前项目比较紧张,故上面代码仅仅实现基本的导出功能, 其实可以通过自定义注解扩展很多功能。如:类型转换,动态宽度等。
大数据量:
关于大数据量这一块, 目前我们生产所解决的方案是分批次查询,使用多个sheet来保存。(此种方式在生产上百万的数据量已经能扛住了)
基于当前的方式进行导出, 个人建议如果数据量不大(10w)以下, 可以使用一个excel来保存。
如果数据量大, 可以分批次查询,调用多次exportToStream()方法进行创建多个excel文件, 最后将所有文件打包成一个zip文件。