esayPOI使用注解進行下載模板(又叫模板導出)、導入、導出


前言

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;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM