配合反射封装和EasyExcel完成Excel读取入库
相信大家在写Java程序时,对于导入Excel文件各种读行读列的超长代码肯定早就恨之入骨了,哪怕是一层又一层的封装下去,整体也是非常的庞大,并且读取速度瓶颈非常大,稍稍上百万条数据就会导致前端响应超时等等问题,体验非常不好,所以今天给大家介绍一个新的工具EasyExcel,下面会贴出他的用法以及非常使用的工具类。
EasyExcel的使用
官方网址:EasyExcel语雀
有什么不解的问题可以去上面的地址官网里查找API文档,里面的使用步骤也说明的非常清晰。
EasyExcel的使用方式非常的多,今天我在这里就说明一种,其他的可以自行去官网了解,好的话不多说上代码。
导入Excel接收的DTO类
@Data
public class UserDTO {
@ExcelProperty(value = "用户名", index = 0)
private String userName;
@ExcelProperty(value = "密码", index = 2)
private String userName;
}
业务层执行导入代码
@Resource
private UserMapper userMapper;
public void importData(MultipartFile file) {
try {
EasyExcel.read(file.getInputStream(), UserDTO.class,
new UserExcelImportListener(userMapper)).sheet().doRead();
} catch (Exception e) {
throw new RuntimeException(e.getLocalizedMessage());
}
}
导入监听类
public class UserExcelImportListener extends AnalysisEventListener<UserDTO> {
private UserMapper userMapper;
private static Map<Integer, CellData> HEADER_MAP = null;
public UserExcelImportListener(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public void invoke(Map<Integer, CellData> headMap, AnalysisContext context) {
HEADER_MAP = headMap;
}
@Override
public void invoke(UserDTO dto, AnalysisContext analysisContext) {
int rowIndex = ((XlsxReadSheetHolder) ((DefaultXlsxReadContext)analysisContext).currentReadHolder()).getRowIndex();
// 判断当前对象里是否有为空的字段
ImportObjectNullUtil.validateByHeaderMap(HEADER_MAP, dto, rowIndex);
// 下面可以进行特殊对象处理,比如数字,时间单独处理等等...
// 最后处理完入库
userMapper.insert(dto);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 防止导入空的Excel
if (analysisContext.readRowHolder().getRowIndex() <= 0) {
throw new RuntimeException("import file row lt zero!");
}
}
}
对象判空工具类
public class ImportObjectNullUtil {
/**
* excel导入根据表头Map判断当前行不能为空的字段是否为空
* @param HEADER_MAP 表头Map
* @param dto 当前行对象
* @param rowIndex 当前行下标
*/
public static void validateByHeaderMap(Map<Integer, CellData> HEADER_MAP, Object dto, int rowIndex) {
CellData cellData = HEADER_MAP.get(validateField(dto, null, null));
if(null != cellData){
log.error(ExceptionMessageUtils.returnRowCellExceptionMessage(rowIndex, cellData.getStringValue()) + "为空");
throw new RuntimeException(ExceptionMessageUtils.returnRowCellExceptionMessage(rowIndex, cellData.getStringValue()) + "为空");
}
}
/**
* 判断对象内不能为空的字段是否为空
* @param object 判空对象
* @param noJudgments 不在导入列的属性数组
* @param emptys 在导入列可以为空的属性数组
* @return 返回为空的属性下标
*/
public static Integer validateField(Object object, String[] noJudgments, String[] emptys){
Integer target = 0;
for (Field f : object.getClass().getDeclaredFields()) {
f.setAccessible(true);
try {
// 不在导入列的属性
if(arrIsNotNull(noJudgments) && Arrays.asList(noJudgments).contains(f.getName())){
continue;
}
// 在导入列可以为空的属性
if (arrIsNotNull(emptys) && Arrays.asList(emptys).contains(f.getName())) {
target ++;
continue;
}
// 在导入列不能为空的列进行判断
if (f.get(object) == null) {
break;
}
// 计算下标
target ++;
} catch (IllegalArgumentException e) {
log.error("对象属性解析异常" + e.getMessage());
return target;
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
log.error("对象属性解析异常" + e.getMessage());
return target;
}
}
return target;
}
/**
* 数组判空
* @param array
* @return
*/
public static boolean arrIsNotNull(Object[] array) {
return null != array && array.length > 0;
}
}
好的,如上就是整个导入过程,上面注释不多,我解释一下,大致可以划分为如下几个点:
- 建立用来接收读取的每一行的类,每个属性对应一个列。
- 建立导入监听,在监听里面进行非空判断,进行类型转换或者其他的逻辑。
- 使用EasyExcel在业务层使用监听读取前端传入的Excel文件流。
- 导入成功 or 导入失败。
最后总结一下为什么使用EasyExcel,以及为什么EasyExcel做导入会更快:
实际上为什么使用EasyExcel最开始已经做了说明了,无非两个词,1:简单,2:快速,简单从代码层面就可以看出来了 短短几十行代码包括了所有的导入过程。
那为什么EasyExcel会比其他的导入工具更快速呢,原因在源码里,通过分析它的源码不难看出,EasyExcel在接收到文件流导出之前,配合es(搜索引擎)进行读取,每次读取前会在本地磁盘建立一个临时空间用来加快读取速度,这就是为什么EasyExcel会比POI其他的导入工具更快更稳。
(以上所有都仅仅代表个人观点,欢迎大家积极指出错误和问题一起讨论交流哈)