在做項目過程中,導入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的列,對我們的對象屬性進行賦值),還有類型判斷的方法,因爲我們不知道對象屬性的類型,因此在賦值時需要判斷屬性的類型然後進行相應的類型轉換。
總之,我們在使用時,根據實際的業務場景,來選擇合理的方式,大數據量數據導入,可以利用多線程來進行處理,有興趣的同學可以自行研究~.~。