全網最全最簡單使用easypoi導入導出Excel的操作手冊
概況
今天做Excel導出時,發(fā)現(xiàn)了一款非常好用的POI框架EasyPoi,其 使用起來簡潔明了。現(xiàn)在我們就來介紹下EasyPoi,首先感謝EasyPoi 的開發(fā)者 Lemur開源
easypoi 簡介
easypoi 是為了讓開發(fā)者快速的實現(xiàn)excel,word,pdf的導入導出,基于Apache poi基礎上的一個工具包。
特性
- 基于注解的導入導出,修改注解就可以修改Excel
- 支持常用的樣式自定義
- 基于map可以靈活定義的表頭字段
- 支持一對多的導出,導入
- 支持模板的導出,一些常見的標簽,自定義標簽
- 支持HTML/Excel轉換
- 支持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.引入依賴
- 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>
- 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
- 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;
- 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;
}
- 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;
導出結果
關于日期格式化的說明
- 如果是導出的實體類(就是說這個實體類是對應導出的Excel的),那么用
@Excel
注解的exportFormat屬性來格式化日期。如下所示:
@Excel(name = "出生日期", exportFormat = "yyyy-MM-dd HH:mm:ss", width = 20)
- 如果是導入的實體類(就是說這個實體類是對應導入的Excel的),那么用
@Excel
注解的importFormat屬性來格式化日期。如下所示:
@Excel(name = "添加時間",importFormat = "yyyy-MM-dd HH:mm:ss",orderNum = "14")
private Date createTime;
@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
參考
作者:碼農飛哥
微信公眾號:碼農飛哥