Poiji:基於列名繫結方式將Excel單元行轉換為JavaBean的開源框架
公司的日常事務中經常需要使用excel進行資料彙總,匯入匯出進行歸類統計分析。
因為沒有廣泛流行的單元行到類轉換/屬性繫結工具,在功能開發之初或者很長一段時間內,
業務系統中我們處理普通excel資料的方法如下:
例如我們公司工程專案中需要到現場部署裝置,
1、從問題域出發我們大概可以建立具有以下屬性的類,用以描述每一行記錄的所具有的屬性以及性狀特徵
public class Device { private String device_id; //裝置編號 private String name; //裝置名稱 privateString model; //裝置型號 private String location; //存放位置 private String serial; //序列號 private String ipv4; //IP地址 private String project_number; //專案編號 private String project_name; //專案名稱 private String constructor_name;//建設人 private String customer_name; //客戶單位名稱 private String project_manager_name; //負責人 private Date lastExamine; //最後一次檢查/巡查日期 }
通過對裝置的基本特性、特徵、用途分析,我們粗略地將他的屬性分為3大類
抽取了一些代表性的屬性,如下:
1、自有屬性
通常是出廠設定或者與生俱來的:名稱、型號、序列號等、
2、業務屬性
專案編號、專案名稱、建設人、客戶單位名稱
3、維保屬性
巡檢日期 等
當然比較合理的設計方法應該建立多個物件,用於描述業務物件(裝置)在系統
功能中參與的一系列行為、事件等類。本文為了配合框架的使用,將屬性糅合到一個類
當中,資料型別也只用了簡單的字串型別來闡述問題。
經過以上構造,一個嶄新鮮活的類,如新生兒般,輕裝上陣,便參與到OOP世界的業務系統中。
編寫程式碼操作
... public static Object getCellValue(Cell cell) { return getCellValue(cell,NULL); } public static String getCellValue(Cell cell, Object defaultValue) { if(cell==null) return defaultValue; cell.setCellType(CellType.STRING); String value = cell.getStringCellValue(); if(StringUtils.isNotEmpty(value)) { return value.trim().replaceAll("\\s+", ""); } return defaultValue; } ... List<Device> list = LinkedList<Device>(); for(int i = 1; i< iRowNums; i++) { Device dev = new Device(); //裝置編號 Cell celldevice_id = row.getCell(0); String device_id = getCellValue(celldevice_id); dev.setDevice_di(device_id); //裝置名稱 ... //專案編號 Cell cellproject_number = row.getCell(6); String project_number = getCellValue(cellproject_number); dev.setProject_number(project_number); list.add(dev); }
當然偷懶的方法也是有的,通過工具類來幫助我少寫程式碼commons-beanutil.jar
中的屬性工具類
public static String getProperty(Object bean,String propertyName) { try { String value = (String)PropertyUtils.getSimpleProperty(bean,propertyName); if(StringUtils.isEmpty(value)) return ""; return value; }catch (Exception ex) { logger.error("getProperty failed:{}",ex); return ""; } } public Cell enumerateCell(Row row, Cell copyOfdev, int offet, int length,String []fields) { for(int i = offet; i <length;i++){ Cell cell = row.getCell(i); String value = getCellValue(cell); String propertyName = fields[i-(offet-1)]; setProperty(copyOfMessage,propertyName,value); } return copyOfMessage; } String fields[] = { "device_id", "name","model", "location","serial", "ipv4", "project_number", "project_name","constructor_name","customer_name","project_manager_name","lastExamine" };
我們給他列名對應的索引值讓他去跑,也能拿到他的屬性,這樣的缺陷是要維護屬性在String陣列中的索引。
電子計算機是一個數字電路系統,按照數值進行計算是他的強項,對於開發人員來說,每次寫類似的程式碼,都要錙銖必較的
計算每一個屬性對應的位置,生怕寫錯了索引帶來錯誤。
然而需求總是隨著時間、政策法令、政治、宗教等客觀因素,以及人的主管意願在變化著。
----以下需求屬於為了講解需求變化場景,可能與事實需求有出入
需求情形1:公司要求,在模板上新增申請人、稽核人
需求情形2:客戶(公安等)要求每個裝置的IP要記錄備案
需求情形3:xxx裝置需要符合國標(GBxxxx),需要新增屬性 AAA
很顯然,我的剛剛描述的類,他的業務屬性和維保屬性肯定會有需要變化的。
公司實際業務場景也可能遇到類似的需求變化,萬一哪一天新增到欄位對業務需求比較重要呢,需要提到前面幾列,
或者列的順序沒有維護好,錯亂了怎麼辦,偶然間閃過一個概念,能不能把Excel單元行繫結到JavaBean,偶然間就構思簡單的
搜尋關鍵字:how to binding excel row to javabean,Google一番找到了本文將要使用的框架Poiji
https://github.com/ozlerhakan/poiji
A tiny library converting excel rows to a list of Java objects based on Apache POI
一個基於Apache POI輕量級工具庫,將Excel行轉換成Java物件列表
按照官方文件,我們把程式碼重構了一下
public class Device { @ExcelCellName("裝置編號") private String device_id; //裝置編號 @ExcelCellName("裝置名稱") private String name; //裝置名稱 @ExcelCellName("裝置型號") private String model; //裝置型號 @ExcelCellName("存放位置") private String location; //存放位置 @ExcelCellName("序列號") private String serial; //序列號 @ExcelCellName("IP地址") private String ipv4; //IP地址 @ExcelCellName("專案編號") private String project_number; //專案編號 @ExcelCellName("專案名稱") private String project_name; //專案名稱 @ExcelCellName("建設人") private String constructor_name;//建設人 @ExcelCellName("客戶單位名稱") private String customer_name; //客戶單位名稱 @ExcelCellName("負責人") private String project_manager_name; //負責人 @ExcelCellName("最後一次檢查/巡查日期") private Date lastExamine; //最後一次檢查/巡查日期 }
在每個屬性上使用註釋,添加註解,把列名加上去,例如 裝置名稱列,寫成
@ExcelCellName("裝置名稱") private String name;
然後一行程式碼,我將獲得批量的java物件
List<Device> devs = Poiji.fromExcel(new File("g:\\device-import-template.xlsx"), Device.class); System.out.println(devs);
筆者在實際開發測試過程中遇到一種情況,excel表頭有空格的情況下無法繫結數值,日常工作過程中難免有人為
操作誤差/失誤,誤輸入空格,這種情況將有可能導致匯入的業務資料就是錯誤的,資料在特定業務場景下運轉起來,
因蝴蝶效應,有可能造成更深遠的影響。
筆者已經修改原始碼,預設去除行首行尾的空格。trimTagName方法
截至寫本文章的時候,官方更新到1.18.1版本,由於1.18.1版本需要POI 4.0版本,現實中生產環境中使用的是3.1.6
筆者修改了一下原始碼,目前相容3.1.6,可以放到生產環境使用。
package com.poiji.deserialize; import com.poiji.bind.Poiji; import com.poiji.deserialize.model.byid.Employee; import com.poiji.deserialize.model.byname.EmployeeByName; import com.poiji.option.PoijiOptions; import com.poiji.option.PoijiOptions.PoijiOptionsBuilder; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.io.File; import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.List; import static com.poiji.util.Data.unmarshallingDeserialize; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; /** * Created by [email protected] on 2018-11-15. */ @RunWith(Parameterized.class) public class DeserializersByNameTagWithWhiteSpaceTest { private String path; private List<Employee> expectedEmployess; private Class<?> expectedException; public DeserializersByNameTagWithWhiteSpaceTest(String path, List<Employee> expectedEmployess, Class<?> expectedException) { this.path = path; this.expectedEmployess = expectedEmployess; this.expectedException = expectedException; } @Parameterized.Parameters(name = "{index}: ({0})={1}") public static Iterable<Object[]> queries() { return Arrays.asList(new Object[][]{ {"src/test/resources/employees-tagwithwhitespace.xlsx", unmarshallingDeserialize(), null}, }); } @Test public void shouldMapExcelToJava() { PoijiOptions options = PoijiOptionsBuilder.settings().datePattern("dd/MM/yyyy").dateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd")).trimCellValue(true).trimTagName(true).build(); try { List<EmployeeByName> actualEmployees = Poiji.fromExcel(new File(path), EmployeeByName.class,options); assertThat(actualEmployees, notNullValue()); assertThat(actualEmployees.size(), not(0)); assertThat(actualEmployees.size(), is(expectedEmployess.size())); EmployeeByName actualEmployee1 = actualEmployees.get(0); EmployeeByName actualEmployee2 = actualEmployees.get(1); EmployeeByName actualEmployee3 = actualEmployees.get(2); Employee expectedEmployee1 = expectedEmployess.get(0); Employee expectedEmployee2 = expectedEmployess.get(1); Employee expectedEmployee3 = expectedEmployess.get(2); assertThat(actualEmployee1.toString(), is(expectedEmployee1.toString())); assertThat(actualEmployee2.toString(), is(expectedEmployee2.toString())); assertThat(actualEmployee3.toString(), is(expectedEmployee3.toString())); } catch (Exception e) { if (expectedException == null) { fail(e.getMessage()); } else { assertThat(e, instanceOf(expectedException)); } } } }
以下奉送上修改後的poiji 1.18.0原始碼
平時空閒時間不多,如有疑問歡迎留言交流。