前言
easypoi功能如同名字easy,主打的功能就是容易,讓一個沒見接觸過poi的人員 就可以方便的寫出Excel導出,Excel模板導出,Excel導入,Word模板導出,通過簡單的注解和模板 語言(熟悉的表達式語法),完成以前復雜的寫法
Excel自適應xls和xlsx兩種格式,word只支持docx模式
Easypoi的目標不是替代poi,而是讓一個不懂導入導出的快速使用poi完成Excel和word的各種操作,而不是看很多api才可以完成這樣工作
注解介紹
@Excel作用到filed上面,是對Excel一列的一個描述
@ExcelCollection表示一個集合,主要針對一對多的導出,比如一個老師對應多個科目,科目就可以用集合表示
@ExcelEntity 表示一個繼續深入導出的實體,但他沒有太多的實際意義,只是告訴系統這個對象里面同樣有導出的字段
@ExcelIgnore和名字一樣表示這個字段被忽略跳過這個導導出
@ExcelTarget 這個是作用於最外層的對象,描述這個對象的id,以便支持一個對象可以針對不同導出做出不同處理
@Excel注解
屬性 | 類型 | 默認值 | 功能 |
---|---|---|---|
name | String | null | 列名,支持name_id |
needMerge | boolean | fasle | 是否需要縱向合並單元格(用於含有list中,單個的單元格,合並list創建的多個row) |
orderNum | String | “0” | 列的排序,支持name_id |
replace | String[] | {} | 值得替換 導出是{a_id,b_id} 導入反過來 |
savePath | String | “upload” | 導入文件保存路徑,如果是圖片可以填寫,默認是upload/className/ IconEntity這個類對應的就是upload/Icon/ |
type | int | 1 | 導出類型 1 是文本 2 是圖片,3 是函數,10 是數字 默認是文本 |
width | double | 10 | 列寬 |
height | double | 10 | 列高,后期打算統一使用@ExcelTarget的height,這個會被廢棄,注意 |
isStatistics | boolean | fasle | 自動統計數據,在追加一行統計,把所有數據都和輸出[這個處理會吞沒異常,請注意這一點] |
isHyperlink | boolean | false | 超鏈接,如果是需要實現接口返回對象 |
isImportField | boolean | true | 校驗字段,看看這個字段是不是導入的Excel中有,如果沒有說明是錯誤的Excel,讀取失敗,支持name_id |
exportFormat | String | “” | 導出的時間格式,以這個是否為空來判斷是否需要格式化日期 |
importFormat | String | “” | 導入的時間格式,以這個是否為空來判斷是否需要格式化日期 |
format | String | “” | 時間格式,相當於同時設置了exportFormat 和 importFormat |
databaseFormat | String | “yyyyMMddHHmmss” | 導出時間設置,如果字段是Date類型則不需要設置 數據庫如果是string 類型,這個需要設置這個數據庫格式,用以轉換時間格式輸出 |
numFormat | String | “” | 數字格式化,參數是Pattern,使用的對象是DecimalFormat |
imageType | int | 1 | 導出類型 1 從file讀取 2 是從數據庫中讀取 默認是文件 同樣導入也是一樣的 |
suffix | String | “” | 文字后綴,如% 90 變成90% |
isWrap | boolean | true | 是否換行 即支持\n |
mergeRely | int[] | {} | 合並單元格依賴關系,比如第二列合並是基於第一列 則{0}就可以了 |
mergeVertical | boolean | fasle | 縱向合並內容相同的單元格 |
fixedIndex | int | -1 | 對應excel的列,忽略名字 |
isColumnHidden | boolean | false | 導出隱藏列 |
例子
@Excel(name = "日期(格式為yyyy-MM-dd)",isImportField = "true", importFormat = "yyyy-MM-dd", databaseFormat = "yyyy-MM-dd",
exportFormat = "yyyy-MM-dd", width = 30)
private String routeDateStr;
@Excel(name = "狀態",width = 20, isColumnHidden = false,replace = {"啟用_1","停用_0"}) @Column(name = "ENABLE_STATUS") private String enableStatus;
@Excel(name = "學生性別", replace = { "男_1", "女_2" }, suffix = "生", isImportField = "true_st") private int sex;
@ExcelEntity和@ExcelCollection的使用
給出一個某個班級選擇選擇某些課的學生以及對應的老師,一個課程對應一個老師,一個課程對應N個學生
課程實體類
@ExcelTarget("courseEntity") public class CourseEntity implements java.io.Serializable { /** 主鍵 */ private String id; /** 課程名稱 */ @Excel(name = "課程名稱", orderNum = "1", width = 25,needMerge="true") private String name; /** 老師主鍵 */ @ExcelEntity(id = "absent") private TeacherEntity mathTeacher; @ExcelCollection(name = "學生", orderNum = "4") private List<StudentEntity> students; }
學生實體類
public class StudentEntity implements java.io.Serializable { private String id; /** * 學生姓名 */ @Excel(name = "學生姓名", height = 20, width = 30, isImportField = "true_st") private String name; /** * 學生性別 */ @Excel(name = "學生性別", replace = { "男_1", "女_2" }, suffix = "生", isImportField = "true_st") private int sex; @Excel(name = "出生日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd", isImportField = "true_st", width = 20) private Date birthday; @Excel(name = "進校日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd") private Date registrationDate; }
老師實體類
@ExcelTarget("teacherEntity") public class TeacherEntity implements java.io.Serializable { private String id; /** name */ @Excel(name = "主講老師_major,代課老師_absent", orderNum = "1", isImportField = "true_major,true_absent",needMerge="true") private String name; }
效果
Excel導入介紹
有導出就有導入,基於注解的導入導出,配置配置上是一樣的,只是方式反過來而已,比如類型的替換 導出的時候是1替換成男,2替換成女,導入的時候則反過來,男變成1 ,女變成2,時間也是類似
導出的時候date被格式化成 2017-8-25 ,導入的時候2017-8-25被格式成date類型
基本是寫法也很簡單,ImportParams 參數介紹下
屬性 | 類型 | 默認值 | 功能 |
---|---|---|---|
titleRows | int | 0 | 表格標題行數,默認0 |
headRows | int | 1 | 表頭行數,默認1 |
startRows | int | 0 | 字段真正值和列標題之間的距離 默認0 |
keyIndex | int | 0 | 主鍵設置,如何這個cell沒有值,就跳過 或者認為這個是list的下面的值,這一列必須有值,不然認為這列為無效數據 |
startSheetIndex | int | 0 | 開始讀取的sheet位置,默認為0 |
sheetNum | int | 1 | 上傳表格需要讀取的sheet 數量,默認為1 |
needSave | boolean | false | 是否需要保存上傳的Excel |
needVerfiy | boolean | false | 是否需要校驗上傳的Excel |
saveUrl | String | “upload/excelUpload” | 保存上傳的Excel目錄,默認是 如 TestEntity這個類保存路徑就是upload/excelUpload/Test/yyyyMMddHHmss_ 保存名稱上傳時間_五位隨機數 |
verifyHanlder | IExcelVerifyHandler | null | 校驗處理接口,自定義校驗 |
lastOfInvalidRow | int | 0 | 最后的無效行數,不讀的行數 |
readRows | int | 0 | 手動控制讀取的行數 |
importFields | String[] | null | 導入時校驗數據模板,是不是正確的Excel |
keyMark | String | “:” | Key-Value 讀取標記,以這個為Key,后面一個Cell 為Value,多個改為ArrayList |
readSingleCell | boolean | false | 按照Key-Value 規則讀取全局掃描Excel,但是跳過List讀取范圍提升性能,僅僅支持titleRows + headRows + startRows 以及 lastOfInvalidRow |
dataHanlder | IExcelDataHandler | null | 數據處理接口,以此為主,replace,format都在這后面 |
Excel導入校驗
校驗,是一個不可或缺的功能,現在java校驗主要是JSR 303 規范,實現方式主流的有兩種:
- Hibernate Validator
- Apache Commons Validator
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.4.0.Final</version> </dependency>
EasyPoi的校驗使用也很簡單,對象上加上通用的校驗規則,然后params.setNeedVerfiy(true);配置下需要校驗就可以了
/** * Email校驗 */ @Excel(name = "Email", width = 25) private String email; /** * 最大 */ @Excel(name = "Max") @Max(value = 15,message = "max 最大值不能超過15" ,groups = {ViliGroupOne.class}) private int max; /** * 最小 */ @Excel(name = "Min") @Min(value = 3, groups = {ViliGroupTwo.class}) private int min; /** * 非空校驗 */ @Excel(name = "NotNull") @NotNull private String notNull; /** * 正則校驗 */ @Excel(name = "Regex") @Pattern(regexp = "[\u4E00-\u9FA5]*", message = "不是中文") private String regex;
我們會返回一個ExcelImportResult 對象,比我們平時返回的list多了一些元素
public class ExcelImportResult<T> { private List<T> list; // 結果集 private List<T> failList; // 是否存在校驗失敗 private boolean verfiyFail; private Workbook workbook; // 數據源 private Workbook failWorkbook; private Map<String, Object> map; }
這個對象必須實現IExcelModel接口,如下
public class ExcelVerifyEntityOfMode extends ExcelVerifyEntity implements IExcelModel { private String errorMsg; @Override public String getErrorMsg() { return errorMsg; } @Override public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } }
實現IExcelDataModel接口獲取錯誤數據的行號
public interface IExcelDataModel { /** * 獲取行號 * @return */ public int getRowNum(); /** * 設置行號 * @param rowNum */ public void setRowNum(int rowNum); }
使用
1、添加依賴
spring項目依賴
1、easypoi-annotation 基礎注解包,作用與實體對象上,拆分后方便maven多工程的依賴管理
2、easypoi-base 導入導出的工具包,可以完成Excel導出,導入,Word的導出,Excel的導出功能
3、easypoi-web 耦合了spring-mvc 基於AbstractView,極大的簡化spring-mvc下的導出功能
<dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-base</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-web</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-annotation</artifactId> <version>3.2.0</version> </dependency>
隨着spring boot的越來越流行,不可免俗的我們也推出了easypoi-spring-boot-starter,方便大家的引用和依賴
<dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>3.3.0</version> </dependency>
2、配置文件中加如下配置
#easypoi啟用覆蓋
spring.main.allow-bean-definition-overriding=true
3、工具類
package com.ljxx.pts.common.util; import cn.afterturn.easypoi.excel.ExcelExportUtil; import cn.afterturn.easypoi.excel.ExcelImportUtil; import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.ImportParams; import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import cn.afterturn.easypoi.excel.entity.result.ExcelImportResult; import org.apache.poi.ss.usermodel.Workbook; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; public class ExcelUtils { /** * excel 導出 * * @param list 數據列表 * @param fileName 導出時的excel名稱 * @param response */ public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) throws IOException { defaultExport(list, fileName, response); } /** * 默認的 excel 導出 * * @param list 數據列表 * @param fileName 導出時的excel名稱 * @param response */ private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) throws IOException { //把數據添加到excel表格中 Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.HSSF); downLoadExcel(fileName, response, workbook); } /** * excel 導出 * * @param list 數據列表 * @param pojoClass pojo類型 * @param fileName 導出時的excel名稱 * @param response * @param exportParams 導出參數(標題、sheet名稱、是否創建表頭,表格類型) */ private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response,
ExportParams exportParams) throws IOException { //把數據添加到excel表格中 Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list); downLoadExcel(fileName, response, workbook); } /** * excel 導出 * * @param list 數據列表 * @param pojoClass pojo類型 * @param fileName 導出時的excel名稱 * @param exportParams 導出參數(標題、sheet名稱、是否創建表頭,表格類型) * @param response */ public static void exportExcel(List<?> list, Class<?> pojoClass, String fileName, ExportParams exportParams,
HttpServletResponse response) throws IOException { defaultExport(list, pojoClass, fileName, response, exportParams); } /** * excel 導出 * * @param list 數據列表 * @param title 表格內數據標題 * @param sheetName sheet名稱 * @param pojoClass pojo類型 * @param fileName 導出時的excel名稱 * @param response */ public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName,
HttpServletResponse response) throws IOException { defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName, ExcelType.XSSF)); } /** * excel 導出 * * @param list 數據列表 * @param title 表格內數據標題 * @param sheetName sheet名稱 * @param pojoClass pojo類型 * @param fileName 導出時的excel名稱 * @param isCreateHeader 是否創建表頭 * @param response */ public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName,
boolean isCreateHeader, HttpServletResponse response) throws IOException { ExportParams exportParams = new ExportParams(title, sheetName, ExcelType.XSSF); exportParams.setCreateHeadRows(isCreateHeader); defaultExport(list, pojoClass, fileName, response, exportParams); } /** * excel下載 * * @param fileName 下載時的文件名稱 * @param response * @param workbook excel數據 */ private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) throws IOException { try { response.setCharacterEncoding("UTF-8"); response.setHeader("content-Type", "application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".xlsx", "UTF-8")); workbook.write(response.getOutputStream()); } catch (Exception e) { throw new IOException(e.getMessage()); } } public static <T> List<T> importExcel(MultipartFile file, Class<T> pojoClass) throws IOException { return importExcel(file.getInputStream(), pojoClass); } /** * excel 導入,有錯誤信息 * * @param file 上傳的文件 * @param pojoClass pojo類型 * @param <T> * @return */ public static <T> ExcelImportResult<T> importExcelMore(MultipartFile file, Class<T> pojoClass) throws IOException { if (file == null) { return null; } try { return importExcelMore(file.getInputStream(), pojoClass); } catch (Exception e) { throw new IOException(e.getMessage()); } } /** * excel 導入 * * @param inputStream 文件輸入流 * @param pojoClass pojo類型 * @param <T> * @return */ public static <T> List<T> importExcel(InputStream inputStream,Class<T> pojoClass) throws IOException { if (inputStream == null) { return null; } ImportParams params = new ImportParams(); params.setTitleRows(1);//表格內數據標題行 params.setHeadRows(1);//表頭行 params.setSaveUrl("/excel/"); params.setNeedSave(true); try { return ExcelImportUtil.importExcel(inputStream, pojoClass, params); } catch (NoSuchElementException e) { throw new IOException("excel文件不能為空"); } catch (Exception e) { throw new IOException(e.getMessage()); } } /** * excel 導入 * * @param inputStream 文件輸入流 * @param pojoClass pojo類型 * @param <T> * @return */ private static <T> ExcelImportResult<T> importExcelMore(InputStream inputStream, Class<T> pojoClass) throws IOException { if (inputStream == null) { return null; } ImportParams params = new ImportParams(); params.setTitleRows(1);//表格內數據標題行 params.setHeadRows(1);//表頭行 params.setSaveUrl("/excel/"); params.setNeedSave(true); params.setNeedVerfiy(true); try { return ExcelImportUtil.importExcelMore(inputStream, pojoClass, params); } catch (NoSuchElementException e) { throw new IOException("excel文件不能為空"); } catch (Exception e) { throw new IOException(e.getMessage()); } } }
導入時注意:工具類中的代碼已經設置了標題行和表頭行。
4、注意事項
1)、excel表格的表頭行名稱必須和@Excel的name屬性值保持一致,否則讀取不到數據。
2)、若導入的字段包含日期類型,那么需要指定導入時的日期的格式importFormat並標明是必導入字段isImportField。
3)、若導出的字段包含日期類型,那么需要指定導出的格式exportFormat。
/** * 日期驗證 * 導出時要將R_DATE日期類型的數據取別名放到routeDateStr中,否則無法導出日期 */ @Transient @Excel(name = "日期(格式為yyyy-MM-dd)",isImportField = "true", importFormat = "yyyy-MM-dd", databaseFormat = "yyyy-MM-dd", exportFormat = "yyyy-MM-dd", width = 30) @NotNull(message = "日期為空") @Pattern(regexp = "^\\d{4}-\\d{1,2}-\\d{1,2}$", message = "日期格式必須是yyyy-MM-dd格式,如1970-02-12") private String routeDateStr; /** * 日期 */ @Column(name = "R_DATE") private Date routeDate;
注意:導出時,從數據庫中查詢數據時,要將R_DATE日期類型字段取別名存入routeDateStr中,否則無法導出日期。此時需要新添加一個String類型的字段routeDateStr,注解@Excel要放到routeDateStr字段上,該字段專門用作導入和導出。我們這里將映射數據庫表字段的實體類屬性與用於Excel導入導出的實體類屬性配置在一個實體類中,注意此時將用於Excel導入導出的實體類屬性加上@Transient注解。屬性類型也為String。當然,也可以將映射數據庫表字段的實體類屬性與用於Excel導入導出的實體類屬性分開配置,此時,用於Excel導入和導出的實體類的字段類型均為String。
4)、性別或狀態字段,使用replace屬性用數字代替文字。
@Excel(name = "狀態",width = 20, isColumnHidden = false,replace = {"啟用_1","停用_0"}) @Column(name = "ENABLE_STATUS") private String enableStatus;
@TableField(value = "sex") @Excel(name = "性別",replace = {"男_0", "女_1"}) private String sex;
5)、要想導出時顯示錯誤信息列,則實體類必須實現IExcelModel接口,並且實體類中提供errorMsg屬性,
@Data @Table(name = "T_ZD_PRI") public class SitePrice implements IExcelModel { 。。。 //錯誤信息 @Transient @Excel(name = "錯誤信息", width = 50, isColumnHidden = false) private String errorMsg; }
6)、isColumnHidden屬性用來控制導出時是否導出該字段,false為默認值,即導出的意思,如果不想導出該字段,則通過反射將isColumnHidden的屬性值改為true
//獲取目標對象的屬性值 Field field = clazz.getDeclaredField(columnName); //獲取注解反射對象 Excel excelAnion = field.getAnnotation(Excel.class); //獲取代理 InvocationHandler invocationHandler = Proxy.getInvocationHandler(excelAnion); Field excelField = invocationHandler.getClass().getDeclaredField("memberValues"); excelField.setAccessible(true); Map memberValues = (Map) excelField.get(invocationHandler); Map memberValues2 = getMemberValues2(clazz, columnName); memberValues2.put("isColumnHidden", true);
7)、使用@Pattern注解的regexp屬性校驗日期和小數
@Transient @Excel(name = "站點經度(小數,小數位2-6位)",width = 30) @NotNull(message = "站點經度為空") @Pattern(regexp = "^([0-9]{1,3})\\.[0-9]{2,6}?$",message = "站點經度必須是小數") private String siteLongitudeStr;
@Transient @Excel(name = "日期(格式為yyyy-MM-dd)",isImportField = "true", importFormat = "yyyy-MM-dd", databaseFormat = "yyyy-MM-dd", exportFormat = "yyyy-MM-dd", width = 30) @NotNull(message = "日期為空") @Pattern(regexp = "^\\d{4}-\\d{1,2}-\\d{1,2}$", message = "日期格式必須是yyyy-MM-dd格式,如1970-02-12") private String routeDateStr;