全網最全最簡單使用easypoi導入導出Excel的操作手冊

概況

今天做Excel導出時,發(fā)現(xiàn)了一款非常好用的POI框架EasyPoi,其 使用起來簡潔明了。現(xiàn)在我們就來介紹下EasyPoi,首先感謝EasyPoi 的開發(fā)者 Lemur開源

easypoi 簡介

easypoi 是為了讓開發(fā)者快速的實現(xiàn)excel,word,pdf的導入導出,基于Apache poi基礎上的一個工具包。

特性

  1. 基于注解的導入導出,修改注解就可以修改Excel
  2. 支持常用的樣式自定義
  3. 基于map可以靈活定義的表頭字段
  4. 支持一對多的導出,導入
  5. 支持模板的導出,一些常見的標簽,自定義標簽
  6. 支持HTML/Excel轉換
  7. 支持word的導出,支持圖片,Excel

常用注解

@Excel注解

@Excel 注解是作用到Filed 上面,是對Excel一列的一個描述,這個注解是必須要的注解,其部分屬性如下:

在這里插入圖片描述
其使用如下,其中orderNum是指定該字段在Excel中的位置,name與Excel中對應的表頭單元格的名稱

    @Excel(name = "主講老", orderNum = "1")
    private String name;

@ExcelCollection 注解

@ExcelCollection 注解表示一個集合,主要針對一對多的導出
比如一個老師對應多個科目,科目就可以用集合表示,作用在一個類型是List的屬性上面,屬性如下:
在這里插入圖片描述
其使用如下所示。

 @ExcelCollection(name = "學生", orderNum = "4")
    private List<StudentEntity> students;

@ExcelEntity注解

@ExcelEntity注解表示一個繼續(xù)深入導出的實體,是作用一個類型為實體的屬性上面,其屬性如下:
在這里插入圖片描述
其使用如下所示。

   @ExcelEntity(id = "major")
    private TeacherEntity chineseTeacher;

@ExcelIgnore 注解

@ExcelIgnore 注解修飾的字段,表示在導出的時候補導出,被忽略。

@ExcelTarget 注解

@ExcelTarget注解作用于最外層的對象,描述這個對象的id,以便支持一個對象,可以針對不同導出做出不同處理,其作用在實體類的上,屬性如下:
在這里插入圖片描述
其使用如下:

@ExcelTarget("scoreIssueReqPOJO")
public class ScoreIssueReqPOJO implements java.io.Serializable{}

EasyPOI的使用

1.引入依賴

  1. SSM 項目,引入依賴
    如果spring的版本是4.x的話引入的easypoi的版本是3.0.1,如果spring是5.x的話引入easypoi的版本是4.0.0
<dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-base</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-web</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-annotation</artifactId>
            <version>4.0.0</version>
        </dependency>
  1. Spring Boot 項目(2.x以上的版本,我demo的版本是2.1.3.RELEASE),引入依賴
	<dependency>
			<groupId>cn.afterturn</groupId>
			<artifactId>easypoi-spring-boot-starter</artifactId>
            <version>4.0.0</version>
		</dependency>

需要注意的是由于easypoi的依賴內部依賴原生的poi,所以,引入了easypoi的依賴之后,需要把原生的poi的依賴刪掉

注解方式導出Excel

導出測試的demo

    @Test
    public void testExportExcel() throws Exception {

        List<CourseEntity> courseEntityList = new ArrayList<>();
        CourseEntity courseEntity = new CourseEntity();
        courseEntity.setId("1");
        courseEntity.setName("測試課程");
        TeacherEntity teacherEntity = new TeacherEntity();
        teacherEntity.setName("張老師");
        teacherEntity.setSex(1);
        courseEntity.setMathTeacher(teacherEntity);

        List<StudentEntity> studentEntities = new ArrayList<>();
        for (int i = 1; i <= 2; i++) {
            StudentEntity studentEntity = new StudentEntity();
            studentEntity.setName("學生" + i);
            studentEntity.setSex(i);
            studentEntity.setBirthday(new Date());
            studentEntities.add(studentEntity);
        }
        courseEntity.setStudents(studentEntities);
        courseEntityList.add(courseEntity);
        Date start = new Date();
        Workbook workbook = ExcelExportUtil.exportExcel( new ExportParams("導出測試", null, "測試"),
                CourseEntity.class, courseEntityList);
        System.out.println(new Date().getTime() - start.getTime());
        File savefile = new File("D:/excel/");
        if (!savefile.exists()) {
            savefile.mkdirs();
        }
        FileOutputStream fos = new FileOutputStream("D:/excel/教師課程學生導出測試.xls");
        workbook.write(fos);
        fos.close();
    }

導出對應的Bean

  1. CourseEntity 類。
@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 = "major")
    private TeacherEntity chineseTeacher;
    /** 老師主鍵 */
    @ExcelEntity(id = "absent")
    private TeacherEntity mathTeacher;

    @ExcelCollection(name = "學生", orderNum = "4")
    private List<StudentEntity> students;
  1. TeacherEntity 類
@Data
public class TeacherEntity {
    /**
     * 學生姓名
     */
    @Excel(name = "教師姓名", height = 20, width = 30, isImportField = "true_st")
    private String name;
    /**
     * 學生性別
     */
    @Excel(name = "教師性別", replace = {"男_1", "女_2"}, suffix = "生", isImportField = "true_st")
    private int sex;
}
  1. StudentEntity 類。
public class StudentEntity implements java.io.Serializable {
    /**
     * id
     */
    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 = "出生日期", exportFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd", isImportField = "true_st", width = 20)
    private Date          birthday;

    @Excel(name = "進校日期", exportFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd")
    private Date registrationDate;

導出結果

在這里插入圖片描述

關于日期格式化的說明

  1. 如果是導出的實體類(就是說這個實體類是對應導出的Excel的),那么用@Excel注解的exportFormat屬性來格式化日期。如下所示:
 @Excel(name = "出生日期", exportFormat = "yyyy-MM-dd HH:mm:ss", width = 20)
  1. 如果是導入的實體類(就是說這個實體類是對應導入的Excel的),那么用@Excel注解的importFormat屬性來格式化日期。如下所示:
   @Excel(name = "添加時間",importFormat =  "yyyy-MM-dd HH:mm:ss",orderNum = "14")
    private Date createTime;
  1. @Excel注解的databaseFormat 屬性是用于數(shù)據(jù)庫的格式不是日期類型,如datetime時用。

注解方式導入Excel

基于注解的導入導出,配置配置上是一樣的,只是方式反過來而已。首先讓我們來看看 ImportParams類,這個類主要是設置導入?yún)?shù),例如表格行數(shù),表頭行數(shù)等等。

ImportParams參數(shù)介紹

在這里插入圖片描述
需要說明的是
1、titleRows表示的是表格標題行數(shù),如果沒有就是0,如果有一個標題就是1,如果是兩個標題就2

2. headRows表示的是表頭行數(shù),默認是1,如果有兩個表頭則需要設置2。

導入情形一:有標題有表頭

我們有如下格式的Excel需要導入:
在這里插入圖片描述
這個Excel有一個標題行,有一個表頭行,所以我們有了如下設置:

  ImportParams params = new ImportParams();
		//設置標題的行數(shù),有標題時一定要有
        params.setTitleRows(1);
		//設置表頭的行數(shù)
        params.setHeadRows(1);

導入的demo

    @Test
    public void haveTitleTest() {
        ImportParams params = new ImportParams();
		//設置標題的行數(shù),有標題時一定要有
        params.setTitleRows(1);
		//設置表頭的行數(shù)
        params.setHeadRows(1);
        String file = Thread.currentThread().getContextClassLoader().getResource("haveTitle.xlsx").getFile();
        List<ScoreIssueReqPOJO> list = ExcelImportUtil.importExcel(
                new File(file),
                ScoreIssueReqPOJO.class, params);
        System.out.println("解析到的數(shù)據(jù)長度是:" + list.size());
        for (ScoreIssueReqPOJO scoreIssueReqPOJO : list) {
                       System.out.println("***********有標題有表頭導入的數(shù)據(jù)是=" + scoreIssueReqPOJO.toString());
        }
    }

導入測試的結果是:

在這里插入圖片描述

導入情形二:有表頭沒有標題

在這里插入圖片描述
只有一個表頭沒有標題的話,我們ImportParams可以用默認的值。

導入的demo

 @Test
    public void notTitleTest() {
        ImportParams params = new ImportParams();
        String file = Thread.currentThread().getContextClassLoader().getResource("notTitle.xlsx").getFile();
        List<ScoreIssueReqPOJO> list = ExcelImportUtil.importExcel(
                new File(file),
                ScoreIssueReqPOJO.class, params);
        System.out.println("解析到的數(shù)據(jù)長度是:" + list.size());
        for (ScoreIssueReqPOJO scoreIssueReqPOJO : list) {
           System.out.println("***********有表頭沒有標題導入的數(shù)據(jù)是=" + scoreIssueReqPOJO.toString());
        }
    }

導入結果如下:
在這里插入圖片描述

導入實體Bean配置

public class ScoreIssueReqPOJO implements java.io.Serializable{
    /**
     * 用戶手機號
     */
    @Excel(name = "個人用戶手機號*",width = 14)
    private String mobile;
    /**
     * 企業(yè)用戶稅號
     */
    @Excel(name = "企業(yè)用戶稅號*",width = 20)
    private String taxNum;
    /**
     * 公司名稱或者用戶名
     */
    @Excel(name = "名稱",width = 20)
    @Length(max =50 ,message = "名稱過長")
    private String realname;
    /**
     * 積分數(shù)量
     * isStatistics 自動統(tǒng)計數(shù)據(jù)
     */
    @Excel(name = "積分數(shù)量*")
    @NotNull(message = "積分數(shù)量不能為空")
    private String scoreNum;
    /**
     * 平臺類型
     */
    @Excel(name = "業(yè)務代碼")
    @Length(max =2 ,message = "業(yè)務代碼過長,最長2個字符(必須由諾諾網分配,請勿亂填)")
    private String platform;
    /**
     * 備注
     */
    @Excel(name = "備注")
    @Length(max =120 ,message = "備注過長,最長120個字符")
    private String typeContent;
}

Excel導入校驗

EasyPoi的校驗使用也很簡單,在導入對象上加上通用的校驗規(guī)則或者這定義的這個看你用的哪個實現(xiàn)
然后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;

使用方式就是在導入時設置needVerfiy屬性為true。導入的demo如下所示:

@Test
    public void basetest() {
        try {
            ImportParams params = new ImportParams();
            params.setNeedVerfiy(true);
            params.setVerfiyGroup(new Class[]{ViliGroupOne.class});
            ExcelImportResult<ExcelVerifyEntity> result = ExcelImportUtil.importExcelMore(
                new File(PoiPublicUtil.getWebRootPath("import/verfiy.xlsx")),
                ExcelVerifyEntity.class, params);
            FileOutputStream fos = new FileOutputStream("D:/excel/ExcelVerifyTest.basetest.xlsx");
            result.getWorkbook().write(fos);
            fos.close();
            for (int i = 0; i < result.getList().size(); i++) {
                System.out.println(ReflectionToStringBuilder.toString(result.getList().get(i)));
            }
            Assert.assertTrue(result.getList().size() == 1);
            Assert.assertTrue(result.isVerfiyFail());
        } catch (Exception e) {
            LOGGER.error(e.getMessage(),e);

導入結果ExcelImportResult

導入之后返回一個ExcelImportResult 對象,比我們平時返回的list多了一些元素

/**
     * 結果集
     */
    private List<T>  list;

    /**
     * 是否存在校驗失敗
     */
    private boolean  verfiyFail;

    /**
     * 數(shù)據(jù)源
     */
    private Workbook workbook;

一個是集合,是一個是是否有校驗失敗的數(shù)據(jù),一個原本的文檔,但是在文檔后面追加了錯誤信息

注意,這里的list,有兩種返回

一種是只返回正確的數(shù)據(jù)
一種是返回全部的數(shù)據(jù),但是要求這個對象必須實現(xiàn)IExcelModel接口,如下
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
獲取錯誤數(shù)據(jù)的行號

public interface IExcelDataModel {

    /**
     * 獲取行號
     * @return
     */
    public int getRowNum();

    /**
     *  設置行號
     * @param rowNum
     */
    public void setRowNum(int rowNum);

}

需要對象實現(xiàn)這個接口

每行的錯誤數(shù)據(jù)也會填到這個錯誤信息中,方便用戶后面自定義處理
看下代碼

 @Test
    public void baseModetest() {
        try {
            ImportParams params = new ImportParams();
            params.setNeedVerfiy(true);
            ExcelImportResult<ExcelVerifyEntityOfMode> result = ExcelImportUtil.importExcelMore(
                    new FileInputStream(new File(PoiPublicUtil.getWebRootPath("import/verfiy.xlsx"))),
                ExcelVerifyEntityOfMode.class, params);
            FileOutputStream fos = new FileOutputStream("D:/excel/baseModetest.xlsx");
            result.getWorkbook().write(fos);
            fos.close();
            for (int i = 0; i < result.getList().size(); i++) {
                System.out.println(ReflectionToStringBuilder.toString(result.getList().get(i)));
            }
            Assert.assertTrue(result.getList().size() == 4);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(),e);
        }
    }

定制化修改

有時候,我們需要定制化一些信息,比如,導出Excel時,我們需要統(tǒng)一Excel的字體的大小,字型等。我們可以通過實現(xiàn)IExcelExportStyler接口或者繼承ExcelExportStylerDefaultImpl類來實現(xiàn)。如下所示:我們定義了一個ExcelStyleUtil工具類繼承了ExcelExportStylerDefaultImpl(樣式的默認實現(xiàn)類),并且將列頭,標題,單元格的字體都設置為了宋體。

public class ExcelStyleUtil extends ExcelExportStylerDefaultImpl {

    public ExcelStyleUtil(Workbook workbook) {
        super(workbook);
    }
	/**
     * 標題樣式
     */	
    @Override
    public CellStyle getTitleStyle(short color) {
        CellStyle cellStyle = super.getTitleStyle(color);
        cellStyle.setFont(getFont(workbook, 11, false));
        return cellStyle;
    }
	 /**
     * 單元格的樣式
     */
    @Override
    public CellStyle stringSeptailStyle(Workbook workbook, boolean isWarp) {
        CellStyle cellStyle = super.stringSeptailStyle(workbook, isWarp);
        cellStyle.setFont(getFont(workbook, 11, false));
        return cellStyle;
    }
	 /**
     * 列表頭樣式
     */
    @Override
    public CellStyle getHeaderStyle(short color) {
        CellStyle cellStyle =  super.getHeaderStyle(color);
        cellStyle.setFont(getFont(workbook, 11, false));
        return cellStyle;
    }
	 /**
     * 單元格的樣式
     */
    @Override
    public CellStyle stringNoneStyle(Workbook workbook, boolean isWarp) {
        CellStyle cellStyle = super.stringNoneStyle(workbook, isWarp);
        cellStyle.setFont(getFont(workbook, 11, false));
        return cellStyle;
    }

    /**
     * 字體樣式
     *
     * @param size   字體大小
     * @param isBold 是否加粗
     * @return
     */
    private Font getFont(Workbook workbook, int size, boolean isBold) {
        Font font = workbook.createFont();
        //字體樣式
        font.setFontName("宋體");
        //是否加粗
        font.setBold(isBold);
        //字體大小
        font.setFontHeightInPoints((short) size);
        return font;
    }

}

然后就是對ExcelExportUtil類進行包裝,如下所示:

public class OfficeExportUtil {

    /**
     *
     */
    private static final Integer EXPORT_EXCEL_MAX_NUM = 20000;

    /**
     * 獲取導出的Workbook對象
     *
     * @param sheetName 頁簽名
     * @param clazz     類對象
     * @param list      導出的數(shù)據(jù)集合
     * @return
     * @author xiagwei
     * @date 2020/2/10 4:45 PM
     */
    public static Workbook getWorkbook(String sheetName, Class clazz, List<?> list) {
        //判斷數(shù)據(jù)是否為空
        if (CollectionUtils.isEmpty(list)) {
            log.info("***********導出數(shù)據(jù)行數(shù)為空!");
            list = new ArrayList<>();
        }
        if (list.size() > EXPORT_EXCEL_MAX_NUM) {
            log.info("***********導出數(shù)據(jù)行數(shù)超過:" + EXPORT_EXCEL_MAX_NUM + "條,無法導出、請?zhí)砑訉С鰲l件!");
            list = new ArrayList<>();
        }
        log.info("***********"+sheetName+"的導出數(shù)據(jù)行數(shù)為"+list.size()+"");
        //獲取導出參數(shù)
        ExportParams exportParams = new ExportParams();
        //設置導出樣式
        exportParams.setStyle(ExcelStyleUtil.class);
        //設置sheetName
        exportParams.setSheetName(sheetName);
        //輸出workbook流
        return ExcelExportUtil.exportExcel(exportParams, clazz, list);
    }

使用的話,我們只需要獲取需要導出的數(shù)據(jù),然后調用OfficeExportUtil的getWorkbook方法。

總結

本文主要介紹了EasyPOI的使用和相關屬性,EasyPOI使用起來還是蠻簡單的。但是有個缺點是導入導出大批量數(shù)據(jù)時性能沒那么好。

參考代碼

https://gitee.com/lemur/easypoi-test

參考

EasyPoi教程



作者:碼農飛哥
微信公眾號:碼農飛哥