前言:在工作中使用EasyExcel处理复杂的大数据量时,会遇到从数据库查询的数据字段存在空值的问题,分享解决思路和解决方案
一开始查询百度的解决方案是使用EasyExcel的内置转换器进行自定义封装处理,代码如下:
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author edward
* @className NullConverter
* @description:
* @date 2022/6/14 16:51
**/
public class NullConverter implements Converter<String> {
/**
* 回到 Java 中的对象类型
*
* @return 支持 Java 类
*/
@Override
public Class supportJavaTypeKey() {
return String.class;
}
/**
* * 返回 excel 中的对象枚举
* * @return 支持 {@link Cell DataTypeEnum}
* */
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 将excel对象转换为Java对象
*
* @param cellData
* Excel 单元格数据。NotNull。
* @param contentProperty
* 内容属性。可空。
* @param globalConfiguration
* 全局配置。NotNull。
* @return 要放入 Java 对象的数据
* @抛出异常
* 例外。
*/
@Override
public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
return "-".equals(cellData.getStringValue())?null:cellData.getStringValue();
}
/**
* 将 Java 对象转换为 excel 对象
*
* @参数值
* Java 数据.NotNull。
* @param contentProperty
* 内容属性。可空。
* @param globalConfiguration
* 全局配置。NotNull。
* @return 数据放入 Excel
* @抛出异常
* 例外。
*/
@Override
public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
return new CellData<>(null == value?"-":value);
}
}
应用:
/**
* ID
*/
@ExcelProperty(converter = NullConverter.class)
private Integer id;
说明:转换主要看convertToExcelData()方法,在这里将null值转换成“-”,但通过转换器的方式有两个弊端:
1.每个字段都要添加@ExcelProperty(converter = NullConverter.class)代码,如果遇到大量的数据字段去填充处理会增加很多工作量。
2.转换器仅支持需要被处理的数据字段,也就是适用于从数据库查询出来已有的数据,如日期格式或性别字段做转换时才生效,对于本身有些字段就是null值时并不能进入到该方法,所以这种解决方案不适用
第二种就是从数据库查询出来的数据,每个数据字段都进行处理,但也是大数据量时工作量很大,当时就想过能否用EasyExcel的监听器去监听处理每个Sheet工作表对象,但研究过后发现并不适用这种场景。所以这种解决方案也不适用
第三种就是EasyExcel内置处理器去自定义封装,当时就想既然查询出来的数据都是要填充到每个单元格的,那么每个单元格都需要做判断处理,那EasyExcel有没有提供相对应的Handler呢,于是就有了下面的解决方案,代码如下:
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import java.util.List;
/**
* @author edward
* @className NullWriteHandler
* @description: 自定义写入处理器(处理空值)
* @date 2022/6/8 20:24
**/
public class CustomizeWriteHandler implements CellWriteHandler {
/**
* 在创建单元格之前调用
*
* @param writeSheetHolder
* @param writeTableHolder
* Nullable。不使用表写入为空。
* @param row
* @param head
* Nullable。填充数据且不带head的情况下为null。
* @param columnIndex
* @param relativeRowIndex
* Nullable。填充数据时为null。
* @param isHead
* 填充数据时始终为假。
*/
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
}
/**
* 创建单元格后调用
*
* @param writeSheetHolder
* @param writeTableHolder
* Nullable。不使用表写入为空。
* @param cell
* @param head
* Nullable。填充数据且不带head的情况下为null。
* @param relativeRowIndex
* Nullable。填充数据时为null。
* @param isHead
* 填充数据时始终为假。
*/
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
/**
* 单元格数据转换后调用
*
* @param writeSheetHolder
* @param writeTableHolder
* Nullable。不使用表写入为空。
* @param cell
* @param head
* Nullable。填充数据且不带head的情况下为null。
* @param cellData
* 可空,添加头时为空。
* @param relativeRowIndex
* Nullable。填充数据时为null。
* @param isHead
* 填充数据时始终为假。
*/
@Override
public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
/**
* 在单元格上的所有操作都完成后调用
*
* @param writeSheetHolder
* @param writeTableHolder
* Nullable。不使用表写入为空。
* @param cell
* @param head
* Nullable。填充数据且不带head的情况下为null。
* @param cellDataList
* Nullable。添加header时为null。填充数据时可能有多个。
* @param relativeRowIndex
* Nullable。填充数据时为null。
* @param isHead
* 填充数据时始终为假。
*/
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
/* 判断是否有进行填充数据的操作 */
if(!isHead){
/* 获取当前单元格的类型 */
switch (cell.getCellType()){
/* 当为String类型是判断是否为null或者"",是的话统一设置成指定值 */
case Cell.CELL_TYPE_STRING:
String stringCellValue = cell.getStringCellValue();
if(null == stringCellValue || "".equals(stringCellValue)){
cell.setCellValue("-");
}
break;
}
}
}
}
说明:前三个默认就行,还是使用最后面的方法,在单元格的所有操作都完成后调用该方法,每个单元格去判断是否为String类型,如果是并且是null值的话在重新设置值到cell对象中,实测问题解决!
应用如下图:这里通过链式调用注册一个处理器作用到一个工作表中,如果遇到多个工作表的话需要对每个工作表进行注册,在全局设置下也能保证对每个工作表和单元格的控制