通用POI讀取Excel封裝成JavaBean
工作中需要開發一個通過客戶上傳的Excel,讀取數據並更新數據庫。解析Excel有很多開源工具,由於項目中有了POI依賴,所以我使用POI來完成此次開發。考慮到直接固定針對此類業務Excel文件開發,雖然簡單,但將來如果又有其它的業務同樣是需要Excel來提供數據,那麼就還需要做Excel的特定解析,顯然這樣做的話,以後有多少個業務,就需要針對性的寫多少個解析代碼,爲了將來能夠少寫點代碼,同時自己身爲中級工程師,代碼要顯得有點逼格,故此寫了這麼一個工具類。
一、添加pom依賴
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.14</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.14</version>
</dependency>
二、表頭與實體屬性關係映射實體
public class ExcelHead {
private String excelName; //Excel名
private String entityName; //實體類屬性名
private boolean required=false; //值必填
public String getExcelName() {
return excelName;
}
public void setExcelName(String excelName) {
this.excelName = excelName;
}
public String getEntityName() {
return entityName;
}
public void setEntityName(String entityName) {
this.entityName = entityName;
}
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
public ExcelHead(String excelName, String entityName, boolean required) {
this.excelName = excelName;
this.entityName = entityName;
this.required = required;
}
public ExcelHead(String excelName, String entityName) {
this.excelName = excelName;
this.entityName = entityName;
}
public ExcelHead(){};
}
三、Excel解析工具類
public class ExcelUtils {
private static final String FULL_DATA_FORMAT = "yyyy/MM/dd HH:mm:ss";
private static final String SHORT_DATA_FORMAT = "yyyy/MM/dd";
/**
* Excel表頭對應Entity屬性 解析封裝javabean
*
* @param classzz 類
* @param in excel流
* @param fileName 文件名
* @param excelHeads excel表頭與entity屬性對應關係
* @param <T>
* @return
* @throws Exception
*/
public static <T> List<T> readExcelToEntity(Class<T> classzz, InputStream in, String fileName, List<ExcelHead> excelHeads) throws Exception {
checkFile(fileName); //是否EXCEL文件
Workbook workbook = getWorkBoot(in, fileName); //兼容新老版本
List<T> excelForBeans = readExcel(classzz, workbook, excelHeads); //解析Excel
return excelForBeans;
}
/**
* 解析Excel轉換爲Entity
*
* @param classzz 類
* @param in excel流
* @param fileName 文件名
* @param <T>
* @return
* @throws Exception
*/
public static <T> List<T> readExcelToEntity(Class<T> classzz, InputStream in, String fileName) throws Exception {
return readExcelToEntity(classzz, in, fileName,null);
}
/**
* 校驗是否是Excel文件
*
* @param fileName
* @throws Exception
*/
public static void checkFile(String fileName) throws Exception {
if (!StringUtils.isEmpty(fileName) && !(fileName.endsWith(".xlsx") || fileName.endsWith(".xls"))) {
throw new Exception("不是Excel文件!");
}
}
/**
* 兼容新老版Excel
*
* @param in
* @param fileName
* @return
* @throws IOException
*/
private static Workbook getWorkBoot(InputStream in, String fileName) throws IOException {
if (fileName.endsWith(".xlsx")) {
return new XSSFWorkbook(in);
} else {
return new HSSFWorkbook(in);
}
}
/**
* 解析Excel
*
* @param classzz 類
* @param workbook 工作簿對象
* @param excelHeads excel與entity對應關係實體
* @param <T>
* @return
* @throws Exception
*/
private static <T> List<T> readExcel(Class<T> classzz, Workbook workbook, List<ExcelHead> excelHeads) throws Exception {
List<T> beans = new ArrayList<T>();
int sheetNum = workbook.getNumberOfSheets();
for (int sheetIndex = 0; sheetIndex < sheetNum; sheetIndex++) {
Sheet sheet = workbook.getSheetAt(sheetIndex);
String sheetName=sheet.getSheetName();
int firstRowNum = sheet.getFirstRowNum();
int lastRowNum = sheet.getLastRowNum();
Row head = sheet.getRow(firstRowNum);
if (head == null)
continue;
short firstCellNum = head.getFirstCellNum();
short lastCellNum = head.getLastCellNum();
Field[] fields = classzz.getDeclaredFields();
for (int rowIndex = firstRowNum + 1; rowIndex <= lastRowNum; rowIndex++) {
Row dataRow = sheet.getRow(rowIndex);
if (dataRow == null)
continue;
T instance = classzz.newInstance();
if(CollectionUtils.isEmpty(excelHeads)){ //非頭部映射方式,默認不校驗是否爲空,提高效率
firstCellNum=dataRow.getFirstCellNum();
lastCellNum=dataRow.getLastCellNum();
}
for (int cellIndex = firstCellNum; cellIndex < lastCellNum; cellIndex++) {
Cell headCell = head.getCell(cellIndex);
if (headCell == null)
continue;
Cell cell = dataRow.getCell(cellIndex);
headCell.setCellType(Cell.CELL_TYPE_STRING);
String headName = headCell.getStringCellValue().trim();
if (StringUtils.isEmpty(headName)) {
continue;
}
ExcelHead eHead = null;
if (!CollectionUtils.isEmpty(excelHeads)) {
for (ExcelHead excelHead : excelHeads) {
if (headName.equals(excelHead.getExcelName())) {
eHead = excelHead;
headName = eHead.getEntityName();
break;
}
}
}
for (Field field : fields) {
if (headName.equalsIgnoreCase(field.getName())) {
String methodName = MethodUtils.setMethodName(field.getName());
Method method = classzz.getMethod(methodName, field.getType());
if (isDateFied(field)) {
Date date=null;
if(cell!=null){
date=cell.getDateCellValue();
}
if (date == null) {
volidateValueRequired(eHead,sheetName,rowIndex);
break;
}
method.invoke(instance, cell.getDateCellValue());
} else {
String value = null;
if(cell!=null){
cell.setCellType(Cell.CELL_TYPE_STRING);
value=cell.getStringCellValue();
}
if (StringUtils.isEmpty(value)) {
volidateValueRequired(eHead,sheetName,rowIndex);
break;
}
method.invoke(instance, convertType(field.getType(), value.trim()));
}
break;
}
}
}
beans.add(instance);
}
}
return beans;
}
/**
* 是否日期字段
*
* @param field
* @return
*/
private static boolean isDateFied(Field field) {
return (Date.class == field.getType());
}
/**
* 空值校驗
*
* @param excelHead
* @throws Exception
*/
private static void volidateValueRequired(ExcelHead excelHead,String sheetName,int rowIndex) throws Exception {
if (excelHead != null && excelHead.isRequired()) {
throw new Exception("《"+sheetName+"》第"+(rowIndex+1)+"行:\""+excelHead.getExcelName() + "\"不能爲空!");
}
}
/**
* 類型轉換
*
* @param classzz
* @param value
* @return
*/
private static Object convertType(Class classzz, String value) {
if (Integer.class == classzz || int.class == classzz) {
return Integer.valueOf(value);
}
if (Short.class == classzz || short.class == classzz) {
return Short.valueOf(value);
}
if (Byte.class == classzz || byte.class == classzz) {
return Byte.valueOf(value);
}
if (Character.class == classzz || char.class == classzz) {
return value.charAt(0);
}
if (Long.class == classzz || long.class == classzz) {
return Long.valueOf(value);
}
if (Float.class == classzz || float.class == classzz) {
return Float.valueOf(value);
}
if (Double.class == classzz || double.class == classzz) {
return Double.valueOf(value);
}
if (Boolean.class == classzz || boolean.class == classzz) {
return Boolean.valueOf(value.toLowerCase());
}
if (BigDecimal.class == classzz) {
return new BigDecimal(value);
}
/* if (Date.class == classzz) {
SimpleDateFormat formatter = new SimpleDateFormat(FULL_DATA_FORMAT);
ParsePosition pos = new ParsePosition(0);
Date date = formatter.parse(value, pos);
return date;
}*/
return value;
}
/**
* 獲取properties的set和get方法
*/
static class MethodUtils {
private static final String SET_PREFIX = "set";
private static final String GET_PREFIX = "get";
private static String capitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
public static String setMethodName(String propertyName) {
return SET_PREFIX + capitalize(propertyName);
}
public static String getMethodName(String propertyName) {
return GET_PREFIX + capitalize(propertyName);
}
}
}
四、測試
Excel表格
創建實體類
public class Excel {
private String name;
private short age;
private byte code;
private char sex;
private int number;
private long phone;
private float stone;
private double money;
private BigDecimal total;
private Date born;
private Short age2;
private Byte code2;
private Character sex2;
private Integer number2;
private Long phone2;
private Float stone2;
private Double money2;
private boolean diel;
private Boolean diel2;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public short getAge() {
return age;
}
public void setAge(short age) {
this.age = age;
}
public byte getCode() {
return code;
}
public void setCode(byte code) {
this.code = code;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public long getPhone() {
return phone;
}
public void setPhone(long phone) {
this.phone = phone;
}
public float getStone() {
return stone;
}
public void setStone(float stone) {
this.stone = stone;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public BigDecimal getTotal() {
return total;
}
public void setTotal(BigDecimal total) {
this.total = total;
}
public Date getBorn() {
return born;
}
public void setBorn(Date born) {
this.born = born;
}
public Short getAge2() {
return age2;
}
public void setAge2(Short age2) {
this.age2 = age2;
}
public Byte getCode2() {
return code2;
}
public void setCode2(Byte code2) {
this.code2 = code2;
}
public Character getSex2() {
return sex2;
}
public void setSex2(Character sex2) {
this.sex2 = sex2;
}
public Integer getNumber2() {
return number2;
}
public void setNumber2(Integer number2) {
this.number2 = number2;
}
public Long getPhone2() {
return phone2;
}
public void setPhone2(Long phone2) {
this.phone2 = phone2;
}
public Float getStone2() {
return stone2;
}
public void setStone2(Float stone2) {
this.stone2 = stone2;
}
public Double getMoney2() {
return money2;
}
public void setMoney2(Double money2) {
this.money2 = money2;
}
public boolean isDiel() {
return diel;
}
public void setDiel(boolean diel) {
this.diel = diel;
}
public Boolean getDiel2() {
return diel2;
}
public void setDiel2(Boolean diel2) {
this.diel2 = diel2;
}
}
測試代碼
@RequestMapping(value = "uploadExcel", produces = "application/json;charset=UTF-8")
public Object uploadExcel(@RequestParam MultipartFile file, @RequestParam String userName) throws Exception {
String name = file.getOriginalFilename();
System.out.println(userName + "上傳:" + name);
List<Excel>list= ExcelUtils.readExcelToEntity(Excel.class,file.getInputStream(),name);
return list;
}
測試結果
結果:
五、Excel中文表頭封裝javabean測試
Excel表
實體類
public class ExcelUser {
private String name;
private Integer age;
private String address;
private Character sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Character getSex() {
return sex;
}
public void setSex(Character sex) {
this.sex = sex;
}
}
測試代碼:
public static void main(String[] args) {
File file = new File("D:\\Excel (2).xlsx");
FileInputStream in = null;
try {
in = new FileInputStream(file);
List<ExcelHead> excelHeads = new ArrayList<ExcelHead>();
ExcelHead excelHead = new ExcelHead("姓名", "name");
ExcelHead excelHead1 = new ExcelHead("性別", "sex");
ExcelHead excelHead2 = new ExcelHead("年齡", "age");
ExcelHead excelHead3 = new ExcelHead("地址", "address", true);
excelHeads.add(excelHead);
excelHeads.add(excelHead1);
excelHeads.add(excelHead2);
excelHeads.add(excelHead3);
List<ExcelUser> list = ExcelUtils.readExcelToEntity(ExcelUser.class, in, file.getName(), excelHeads);
for (ExcelUser excelUser : list) {
System.out.println(excelUser.getName() + ":" + excelUser.getSex() + ":" + excelUser.getAge() + ":" + excelUser.getAddress());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(in!=null) {
try {
in.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
測試結果:
可以看到,我在創建表頭關係映射的時候,默認姓名不進行非空校驗,而對於地址則進行了非空校驗,這裏拋出了一個異常。
修改後繼續測試
測試成功。
本次完成了通用的Excel解析並封裝成javabean工具類,並自動適應各種類型。不足之處:可以看到每讀取一行就會重新將每一列與實體類的屬性進行遍歷比對,感覺此處需要優化。我的想法是在讀取第一行,也就是表頭的時候就記錄此列與實體類屬性的對應下標,後面則不進行遍歷,直接根據下標取值,歡迎大神提出不足之處,對我的代碼進行優化。