在做项目过程中,导入excel数据应该是很常见的操作,我们都是如何去做他呢,肯定做法是多种多样的,我估计大多数同学都习惯这样的一种方式,直接对我们的对我们所需要的实体bean进行挨个set值,如下面这种方式:
定义日期格式:
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
正常编码风格:
public static List<ExcelImportBean> customConvertToList(InputStream inputStream) {
List<ExcelImportBean> list = new ArrayList<>();
try {
//创建工作簿
Workbook workbook = WorkbookFactory.create(inputStream);
//创建工作表sheet
Sheet sheet = workbook.getSheetAt(0);
//sheet中数据的行数
int rows = sheet.getPhysicalNumberOfRows();
//表头单元格个数
int cells = sheet.getRow(0).getPhysicalNumberOfCells();
for (int i = 1; i < rows; i++) {
Row row = sheet.getRow(i);
ExcelImportBean bean = new ExcelImportBean();
bean.setNum(Integer.valueOf(getCellValue(row.getCell(0))));
bean.setStr(getCellValue(row.getCell(1)));
bean.setDate(DATE_FORMAT.parse(getCellValue(row.getCell(2))));
list.add(bean);
}
} catch (Exception e) {
log.error("excel parse exception");
} finally {
try {
inputStream.close();//流关闭
} catch (Exception e2) {
log.error("io close exception");
}
}
return list;
}
但是如果我们有多处不同对象类型的导出呢,我们就需要写多个这种代码,对我们的对象属性进行set值,看起来代码显得很那啥~~~,这样的代码怎么对他进行封装呢,我们可以利用反射对对象属性进行set值,这样我们就可以做成一个通用的工具类,方便使用,下面介绍下反射的方式的代码实现:
定义缓存(用于存储反射创建的方法信息):
private static final Map<String,Object> DATA_CACHE = new ConcurrentHashMap<>();
将excel模板数据转换为list:
/**
*@desc: 模板数据转换为list
*
*@date: 下午5:00
*/
public static <T> List<T> convertToList(Class<T> clazz, InputStream inputStream) {
List<T> list = new ArrayList<>();
try {
//创建工作簿
Workbook workbook = WorkbookFactory.create(inputStream);
//创建工作表sheet
Sheet sheet = workbook.getSheetAt(0);
//sheet中数据的行数
int rows = sheet.getPhysicalNumberOfRows();
//表头单元格个数
int cells = sheet.getRow(0).getPhysicalNumberOfCells();
Field[] fields = clazz.getDeclaredFields();
for (int i = 0;i<cells;i++){
Field field = fields[i];
String methodName = buildMethodName(field.getName());
DATA_CACHE.put(field.getName(),methodName);
DATA_CACHE.put(clazz.getName()+field.getName(),clazz.getMethod(methodName, new Class[]{field.getType()}));
}
for (int i = 1; i < rows; i++) {
Row row = sheet.getRow(i);
T o = clazz.getConstructor(new Class[]{}).newInstance(new Object[]{});
for(int index = 0;index < cells;index ++){
Cell cell = row.getCell(index);
if(cell == null){
cell = row.createCell(index);
}
Field field = fields[index];
String methodName = (String) DATA_CACHE.get(field.getName());
Method method = (Method) DATA_CACHE.get(clazz.getName()+field.getName());
setAttrValue(method,o, getCellValue(cell),field.getType());
}
list.add(o);
}
} catch (Exception e) {
log.error("excel parse exception");
} finally {
try {
inputStream.close();//流关闭
} catch (Exception e2) {
log.error("io close exception");
}
}
return list;
}
set方法构建:
/**
* @desc: 构建set方法名称
* @auther: Tjs
* @date: 下午2:12
*/
private static String buildMethodName(String attr) {
Pattern pattern = Pattern.compile("[a-zA-Z]");
Matcher matcher = pattern.matcher(attr);
StringBuilder sb = new StringBuilder("set");
if (matcher.find() && attr.charAt(0) != '_') {
sb.append(matcher.replaceFirst(matcher.group().toUpperCase()));
} else {
sb.append(attr);
}
return sb.toString();
}
设置属性值:
/**
* @desc: 设置属性值
* @auther: Tjs
* @date: 下午2:12
*/
private static void setAttrValue(Method method,Object o,String val,Class<?> type) {
try {
//参数类型转换 把String放到第一个if里是因为我们导入的数据大都是字符串类型,这样可以减少不必要的判断次数,当数据量很大时候可以体现出来
if(type == java.lang.String.class){
method.invoke(o, type.cast(val));
}else if (type == java.util.Date.class) {
Date date = null;
try {
date = DATE_FORMAT.parse(val);
} catch (Exception e) {
log.error("日期转换错误");
}
method.invoke(o, date);
} else if (type == int.class || type == java.lang.Integer.class || type == long.class || type == java.lang.Long.class) {
val = val.substring(0, val.lastIndexOf(".") == -1 ? val.length() : val.lastIndexOf("."));
method.invoke(o, Integer.valueOf(val));
} else if (type == float.class || type == java.lang.Float.class) {
method.invoke(o, Float.valueOf(val));
} else if (type == double.class || type == java.lang.Double.class) {
method.invoke(o, Double.valueOf(val));
} else if (type == byte.class || type == java.lang.Byte.class) {
method.invoke(o, Byte.valueOf(val));
} else if (type == boolean.class || type == java.lang.Boolean.class) {
method.invoke(o, Boolean.valueOf(val));
} else {
method.invoke(o, type.cast(val));
}
} catch (Exception e) {
log.error("参数转换错误");
}
}
获取列值:
/**
* @desc: 获取列值
* @auther: Tjs
* @date: 下午2:12
*/
private static String getCellValue(Cell cell) {
switch (cell.getCellType()) {
case Cell.CELL_TYPE_STRING:
return cell.getStringCellValue();
case Cell.CELL_TYPE_NUMERIC:
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
Date date = cell.getDateCellValue();
return DATE_FORMAT.format(date);
} else {
return NumberToTextConverter.toText(cell.getNumericCellValue());
}
case Cell.CELL_TYPE_BOOLEAN:
Object retBool = cell.getBooleanCellValue();
return retBool.toString();
case Cell.CELL_TYPE_FORMULA:
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
Date date = cell.getDateCellValue();
return DATE_FORMAT.format(date);
}
return cell.getCellFormula();
case Cell.CELL_TYPE_ERROR:
Object retError = cell.getErrorCellValue();
return retError.toString();
case Cell.CELL_TYPE_BLANK:
return "";
default:
return cell.toString();
}
}
ExcelImportBean对象:
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ExcelImportBean {
private Integer num;
private String str;
private Date date;
private String other;
}
调用:
@ApiOperation("导入")
@PostMapping("/import")
public Result excelImport(@RequestParam MultipartFile file) throws IOException {
List<ExcelImportBean> list = ExcelUtil.convertToList(ExcelImportBean.class,file.getInputStream());
//业务处理list
return new Result();
}
这样就可以愉快的使用了,很简单。
在此我们需要注意的是我们的对象bean:ExcelImportBean中的字段属性设置要按照我们需要导出的列的顺序设置(这是约定)
即:我们的对象的第一条属性须是数字-num,第二条须是字符串str,第三条须是日期-date,对象的其他不需要导入的字段排在这三个字段之后即可。
反射的方式虽然使用起来简单,不过当数据量很大的时候,性能会低,没有硬编码的方式快,为什么呢,反射我们应该知道,java运行状态下,可以获取类的相关信息,信息是通过查找获取的,需要花费点时间(当然这个时间很短),而我们正常通过new 对象的方式呢,那是在jvm编译的时候类的相关属性及方法都放在堆栈里了,直接拿来用就行了(可以少走弯路,直奔主题),数据量少我们可以忽略反射带来的影响,当很大时候用反射每次都需要走那么多的路,一次两次没事,量级上十万百万时候我们就能看出来了,速度会比硬编码的慢,慢是以内单纯的反射构建占其中一部分原因(利用缓存可以解决,上面的方式已经加了缓存了),另一部分原因是因为多了层循环(遍历的是excel的列,对我们的对象属性进行赋值),还有类型判断的方法,因为我们不知道对象属性的类型,因此在赋值时需要判断属性的类型然后进行相应的类型转换。
总之,我们在使用时,根据实际的业务场景,来选择合理的方式,大数据量数据导入,可以利用多线程来进行处理,有兴趣的同学可以自行研究~.~。