使用 POI 封装工具类处理 Excel 表格文件--导入篇
有关于系统中导入 Excel 表格文件,其实大家普遍见过或者使用过。但是相较于网上提供的 EasyPOI 或者 Hutool 工具类里面封装好的 Excel 处理方法(马总推荐使用的工具类),其实在很多情况下不太符合我们的导出或者导入场景,总是需要手动处理下,很是繁琐,毕竟不是所有的表格数据我们都需要,或不是所有的数据都需要我们导出。所以这里手动处理下 POI 类(其实人家封装的已经很完整了,然而还是不太好用),让这个处理的方法更贴合实际使用。
云中谁寄锦书来?雁字回时,月满西楼。
构思
我们使用 Excel 导入的目的是什么?
- 通常我们使用 Excel 导入的目的说白了就是为了获取表格里面的数据,然后整理后提炼出来被我们使用或者存入到数据库中。但是很多情况下,不是所有的列数据都是我们需要的,所以我们首先要明确我们需要什么。我这里处理使用常量来定义了导入的模板。(当然大家还可以使用枚举,或者选择和前端交互,肆意而为,毕竟办法总比困难多-狗头保命 )
模板定义: 0| 备注(属性名)| 属性名
public static class TempLate{
@ConstantAnnotation("001")
public static final String MODE_CONTENT="0|凭证号|voucherNo,1|会计年度|accountYear,2|会计月度|accountMonth,3|数据来源|dataSource,4|存储类型(0. 电子 1. 电子和纸质)|saveType,5|存储类型名称|voucherName,6|摘要|accountAbstract,7|关键字|keyWord,8|凭证类型|voucherTypeId,9|凭证类型名称|voucherTypeName,10|凭证状态0 待装册 1 已装册|voucherState,11|创建时间|creatTime,12|单位编码|orgCode,13|单位名称|orgName";
}
知道了我们要获取的数据有哪些后,我们怎么做匹配?
- 当我们知道了我们需要表格数据的哪一列,我们心中其实也就明白了这列数据对应我们数据模型的哪一个字段,这样在后面处理的时候只要匹配成功即可。
所以我们需要用到反射的一点点知识,使用好 Field,然后简单的做几个判断,就可以完美匹配了。
T object = instance.newInstance();
Field[] fields= object.getClass().getDeclaredFields();
注:instance 是一个 Class 类型的入参,用来放我们需要储存数据的数据模型类.class 还有就是 fields,是我们.class 里面的所有属性。(获取私有属性要把 accessible 赋值 true)
- 接下来我们拿到属性数组后,可以循环比较类型或者属性名来操作并赋值。
if (mapHeader.get(s).equals(f.getName())) {
//获取私有变量
f.setAccessible(true);
f.set(object, cell.getStringCellValue());
如何调用?我们该传什么进行处理?
- 首先传一个要处理的 Excel 文件,然后我们传个模板,再传一个我们想要返回结果集合的类型.class 即可。例:
List<EaVoucher> list=POIUtils.excelToEmployee(file,EaVoucher.class,modeText);
模板处理
public static Map<String, String> stringToMap(String text) throws Exception {
Map<String, String> mapHeader = new HashMap<>();
String[] decollator = text.split(",");
if (decollator.length > 0) {
for (String s : decollator) {
String[] desc = s.split("\\|");
if (desc.length < 3 || "".equals(desc[0]) || "".equals(desc[2])) {
throw new Exception("模板格式有误,请检查!");
}
mapHeader.put(desc[0], desc[2]);
}
}
return mapHeader;
}
然后我们就可以使用这个工具类了。源码方法如下:
/**
* @Description: excel文件解析成相应类型的List
* @Param: [file, instance, modeText]
* @return: java.util.List<T>
* @Author: ShiDunKai
* @Date: 2020/5/29
*/
public static <T> List<T> excelToEmployee(MultipartFile file, Class<T> instance, String modeText) throws Exception {
//获取导入模板
Map<String, String> mapHeader = stringToMap(modeText);
//接收list
List<T> tList = new ArrayList<>();
//创建一个workbook对象
HSSFWorkbook workbook = new HSSFWorkbook(file.getInputStream());
//获取workbook中标签页的数量
int numberOfSheets = workbook.getNumberOfSheets();
for (int sheetNumber = 0; sheetNumber < numberOfSheets; sheetNumber++) {
HSSFSheet sheetAt = workbook.getSheetAt(sheetNumber);
//获取行
int physicalNumberOfRows = sheetAt.getPhysicalNumberOfRows();
for (int rowNumber = 0; rowNumber < physicalNumberOfRows; rowNumber++) {
if (0 == rowNumber) {
//跳过标题行 因为我们这里不做任何处理,所以有没有continue,效果都是一样的,精简才是王道。
} else {
//解析第一行,与模板进行匹配
HSSFRow row = sheetAt.getRow(rowNumber);
//空行判断
if (null == row) {
continue;
}
//一行一个对象
T object = instance.newInstance();
Field[] fields = object.getClass().getDeclaredFields();
//获取列
int physicalNumberOfCells = row.getPhysicalNumberOfCells();
if (physicalNumberOfCells >= mapHeader.size()) {
for (String s : mapHeader.keySet()) {
HSSFCell cell = row.getCell(Integer.parseInt(s));
for (Field f:fields) {
if (mapHeader.get(s).equals(f.getName())) {
//获取私有变量
f.setAccessible(true);
f.set(object, cell.getStringCellValue());
} else {
throw new Exception("模板属性名与对象属性不符,请检查模板是否正确配置。");
}
}
}
} else {
throw new Exception("表格列数量与实际要导出的列数不符,无法获取某些列数据,请检查模板或者表格文件是否正确。");
}
tList.add(object);
}
}
}
return tList;
}
后:
是不是很简单,模板我们可以随时调整,如果后期需要做集成,我们完全可以想办法和前端进行交互,使工具类的扩展性再提一个层次。前期业务量小的话完全可以自定义模板,不影响使用效果的。😋
至于导入我们如何做处理,其实大致思路和上面是一样的,只不过现在已经半夜 0:08,早睡保命要紧,明天还要早起吃早点,还要工作。。。那就拖到周六日给大家分享吧(或许拖更久,别急:trollface: )
其实这个博客在电脑上看效果是最好的,但是大家平时还是看手机比较多,所以微信公众号随后也会完成更新推送,欢迎关注留言讨论(
留言了我也不一定回复)。
贴图:
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于