提出問題:
通過銷售地圖項目和目前的評分系統的項目都需要用到解析excel,並且每次因爲excel中列名的不同和對應的實體類的不同,每一次都需要重新寫一個解析excel的方法,代碼之長很複雜也很麻煩寫,每一次動輒就幾十行代碼,解析一個兩個還可以要是需要解析四個五個呢,浪費時間之多,而且還無用,因此研究了一下是否可以寫一個通用的excel解析
分析問題
既然要寫一個通用excel解析,一步步分析,首先我們需要讓excel每一列和我們要轉換成的實體類的屬性對應起來,有那麼多不同的excel對應不同的實體類我們要怎麼判斷是哪一個呢?然後一個entity中也會有多個屬性我們如何判斷哪一列對應的哪一個屬性呢?
解決問題
首先我們解決哪一列對應的entity中的屬性是什麼的問題,我配置了一個xml文件,發現可以很好地解決這個問題。
cel代表的是excel只中的第幾列,value代表對應的entity中的屬性名
那麼還剩下第二個問題了這個問題解決其實也比較簡單,我們只需要用泛型和反射的配合就可以很好的解決對應的是哪一個實體類的問題,下面我們配合解析步驟詳細說明一下,代碼比較多不太好拆分因此詳細的步驟解釋全部在代碼註釋中
/**
*解析excel
* @param multipartFile 文件流
* @param webAPP 項目webapp路徑
* @param t 指返回的集合中的entity的類型
* @param jsonResult 結果信息
* @param dateFormate 日期格式
* @param file 配置文件對象
* @return 解析集合
* @throws Exception
*/
public List<T> resolveExcel(MultipartFile multipartFile, String webAPP, T t, JsonResult jsonResult,String dateFormate,File file) throws Exception {
List<T> list = new ArrayList<>();
//1.創建Reader對象
SAXReader reader = new SAXReader();
//2.加載xml
Document document = reader.read(file);
//獲得根元素,entitys
Element rootElement = document.getRootElement();
//獲得根元素的子元素,entity
List<Element> elements = rootElement.elements();
if(elements.size() == 1 &&"entity".equals( elements.get(0).getName())){
//獲得entity的子元素 column
List<Element> elementList = elements.get(0).elements();
//本次測試用的文件路徑
String excelPath = "F:\\demo.xls";
//創建這個文件
File excel = new File(excelPath);
//以下注釋爲實際項目時的如何創建從前端接收的文件
// String upload = webAPP+"//upload//"+multipartFile.getOriginalFilename();
// File file = new File(upload);
// File excel = new FileUploadUtil(file,multipartFile).uploadFile(false);
//判斷文件是否存在
if(excel.isFile() && excel.exists()){
String[] split =excel.getName().split("\\.");
Workbook wb = null;
if("xls".equals(split[1])){
FileInputStream fis = new FileInputStream(excel);
wb = new HSSFWorkbook(fis);
}else if("xlsx".equals(split[1])){
wb = new HSSFWorkbook(POIFSFileSystem.create(excel));
}else {
jsonResult.setFailReason("文件格式錯誤");
return null;
}
Sheet sheet = wb.getSheetAt(0);
//第一行一般是文字,所以不讀取第一行
int fistRowNumber = sheet.getFirstRowNum()+1;
//得到最後一行
int lastRowNumber = sheet.getLastRowNum();
//遍歷所有行
for(int rIndex = fistRowNumber;rIndex<=lastRowNumber;rIndex++){
//實例化一個entity用來存儲遍歷excel得到的數據
Class classes = (Class<T>) t.getClass();
t = (T) classes.newInstance();
//獲得行
Row row =sheet.getRow(rIndex);
if(row != null){
int cel = 0;
while (true){
//判斷第幾行第幾列的數據是否爲空
if(row.getCell(cel) != null){
//遍歷xml中的column中的標籤
for(Element element :elementList){
//如果對應的cel和當前正在解析的列 相等
if(Integer.parseInt(element.attributeValue("cel"))-1==cel){
//得到這個類的全部屬性
Field [] fields = classes.getDeclaredFields();
//遍歷屬性
for(Field field : fields){
//如果cel對應的value存在該entity中 就賦值
if(element.attributeValue("value").equals(field.getName())){
field.setAccessible(true);
//將值賦到entity中
conversionType(field,row.getCell(cel).toString(),t,dateFormate);
}
}
}
}
//列數加一
cel++;
//如果當前列爲空並且已經大於最後一列
}else if(cel >= row.getLastCellNum()){
//將entity加到list中
list.add(t);
break;
}
}
}
}
}
}
return list;
}
/**
* 判斷entity屬性是什麼類型(只能判斷基本類型)
* @param field 當前屬性
* @param value 當前屬性的值
* @param t 所在的entity
* @param dateFormat 如果有日期類型所對應的日期格式
* @throws Exception
*/
public void conversionType(Field field,String value,T t,String dateFormat) throws Exception {
//或得到該屬性的類型
Type genericType = field.getGenericType();
if(genericType.toString().endsWith("String")){
field.set(t,value);
}else if(genericType.toString().endsWith("int")||"Integer".endsWith(genericType.toString())){
field.set(t,Integer.parseInt(value));
}else if("Long".endsWith(genericType.toString()) || "long".endsWith(genericType.toString())){
field.set(t,new Long(value));
}else if("char".endsWith(genericType.toString())){
field.set(t,value.charAt(0));
}else if("boolean".endsWith(genericType.toString()) || "Boolean".endsWith(genericType.toString())){
field.set(t,Boolean.valueOf(value));
}else if("double".endsWith(genericType.toString()) || "Double".endsWith(genericType.toString())){
field.set(t,Double.valueOf(value));
}else if("float".endsWith(genericType.toString()) || "Float".endsWith(genericType.toString())){
field.set(t,Float.valueOf(value));
}else if("java.util.Date".equals(genericType.getTypeName())){
field.set(t,new SimpleDateFormat(dateFormat).parse(value));
}
}
然後我們創建一個excel表格
接下來我們來測試一下我們寫的解析excel效果怎麼樣
public static void main(String[] args) {
try {
//根據配置文件位置創建文件
File file = new File("src/main/resource/ExcelUtils.xml");
/**
* 調用我們所寫的方法
* 第一個參數:前端傳入的文件流,用的本地文件測試因此是null
* 第二個參數:本地測試,所以也沒有文件在項目中對應的路徑
* 第三個參數:轉換之後對應的實體類
* 第四個參數:存儲錯誤信息類
* 第五個參數:如果有日期格式的解析對應的日期格式
* 第六個參數:創建的xml配置文件
*/
List<User> users= new ExcelUtils().resolveExcel(null,"",new User(),new JsonResult(),"yyyy.MM.dd",file);
for(User user :users){
System.out.print("username:"+user.getUsername()+"\t");
System.out.print("password:"+user.getPassword()+"\t");
System.out.print("startTime:"+user.getStartTime()+"\r\n");
System.out.println("================================================");
}
} catch (Exception e) {
e.printStackTrace();
}
}
然後我們來看一下控制檯的打印結果
總結
1.創建一個xml來對應excel中列和entity中屬性的映射關係
2.使用泛型當做參數擺脫侷限於某一個entity
3.通過xml中的映射關係來對所要轉換的實體類進行賦值
4.將賦值好的entity加到list中
5.返回list
經過以上步驟不難發現我們已經可以解決用一個通用的解析excel的方法來解析大多數常見的excel