概况
今天做Excel导出时,发现了一款非常好用的POI框架EasyPoi,其 使用起来简洁明了。现在我们就来介绍下EasyPoi,首先感谢EasyPoi 的开发者 Lemur开源
easypoi 简介
easypoi 是为了让开发者快速的实现excel,word,pdf的导入导出,基于Apache poi基础上的一个工具。
使用
- 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以上的版本),引入依赖
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-spring-boot-starter</artifactId>
<version>4.0.0</version>
</dependency>
需要注意的是由于easypoi的依赖内部依赖原生的poi,所以,引入了easypoi的依赖之后,需要把原生的poi删除
特性
- 基于注解的导入导出,修改注解就可以修改Excel
- 支持常用的样式自定义
- 基于map可以灵活定义的表头字段
- 支持一对多的导出,导入
- 支持模板的导出,一些常见的标签,自定义标签
- 支持HTML/Excel转换
- 支持word的导出,支持图片,Excel
注解方式
- 常用注解
- @Excel 作用到filed 上面,是对Excel一列的一个描述,这个注解是必须要的注解,其部分属性如下:
在这里插入图片描述 其中 name_id 的说明:例如:
代码语言:javascript复制@ExcelTarget("teacherEntity")
public class TeacherEntity implements java.io.Serializable {
/** name */
@Excel(name = "主讲老师_teacherEntity,代课老师_absent", orderNum = "1", mergeVertical = true,needMerge=true,isImportField = "true_major,true_absent")
private String name;
这里的@ExcelTarget 表示使用teacherEntity这个对象是可以针对不同字段做不同处理 同样的ExcelEntity 和ExcelCollection 都支持这种方式 当导出这对象时,name这一列对应的是主讲老师,而不是代课老师还有很多字段都支持这种做法
- @ExcelCollection 表示一个集合,主要针对一对多的导出 比如一个老师对应多个科目,科目就可以用集合表示,属性如下:
在这里插入图片描述
- @ExcelEntity表示一个继续深入导出的实体
在这里插入图片描述
- @ExcelIgnore 和名字一样表示这个字段被忽略跳过这个导出
- @ExcelTarget 作用于最外层的对象,描述这个对象的id,以便支持一个对象 可以针对不同导出做出不同处理
在这里插入图片描述
注解方式导出Excel参考demo
代码语言:javascript复制 /**
* 基本导出测试
*
* @throws Exception
*/
@Test
public void testExportExcel() throws Exception {
Date start = new Date();
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("2412312", "测试", "测试"),
CourseEntity.class, list);
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();
}
CourseEntity 类。
代码语言:javascript复制@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;
StudentEntity 类。
代码语言:javascript复制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 属性是用于数据库的格式不是日期类型datetime类型时用。 注解方式导入Excel
1. 介绍
基于注解的导入导出,配置配置上是一样的,只是方式反过来而已,比如类型的替换 导出的时候是1替换成男,2替换成女,导入的时候则反过来,男变成1 ,女变成2,时间也是类似导出的时候date被格式化成 2017-8-25 ,导入的时候2017-8-25被格式成date类型
代码语言:javascript复制 @Test
public void test2() {
ImportParams params = new ImportParams();
long start = new Date().getTime();
List<MsgClient> list = ExcelImportUtil.importExcel(
new File(PoiPublicUtil.getWebRootPath("import/ExcelExportMsgClient.xlsx")),
MsgClient.class, params);
System.out.println(new Date().getTime() - start);
System.out.println(list.size());
System.out.println(ReflectionToStringBuilder.toString(list.get(0)));
}
在这里插入图片描述
ImportParams
ImportParams这个类是用于设置导入参数的。我们来看下这个类
代码语言:javascript复制public class ImportParams extends ExcelBaseParams {
public static final String SAVE_URL = "/excel/upload/excelUpload";
/**
* 表格标题行数,默认0
*/
private int titleRows = 0;
/**
* 表头行数,默认1
*/
private int headRows = 1;
/**
* 字段真正值和列标题之间的距离 默认0
*/
private int startRows = 0;
/**
* 主键设置,如何这个cell没有值,就跳过 或者认为这个是list的下面的值
* 大家不理解,去掉这个
*/
private Integer keyIndex = null;
/**
* 开始读取的sheet位置,默认为0
*/
private int startSheetIndex = 0;
/**
* 上传表格需要读取的sheet 数量,默认为1
*/
private int sheetNum = 1;
/**
* 是否需要保存上传的Excel,默认为false
*/
private boolean needSave = false;
/**
* 校验组
*/
private Class[] verifyGroup = null;
/**
* 是否需要校验上传的Excel,默认为false
*/
private boolean needVerify = false;
/**
* 校验处理接口
*/
private IExcelVerifyHandler verifyHandler;
/**
* 保存上传的Excel目录,默认是 如 TestEntity这个类保存路径就是
* upload/excelUpload/Test/yyyyMMddHHmss_***** 保存名称上传时间_五位随机数
*/
private String saveUrl = SAVE_URL;
/**
* 最后的无效行数
*/
private int lastOfInvalidRow = 0;
/**
* 手动控制读取的行数
*/
private int readRows = 0;
/**
* 导入时校验数据模板,是不是正确的Excel
*/
private String[] importFields;
/**
* 导入时校验excel的标题列顺序。依赖于importFields的配置顺序
*/
private boolean needCheckOrder = false;
/**
* Key-Value 读取标记,以这个为Key,后面一个Cell 为Value,多个改为ArrayList
*/
private String keyMark = ":";
/**
* 按照Key-Value 规则读取全局扫描Excel,但是跳过List读取范围提升性能
* 仅仅支持titleRows headRows startRows 以及 lastOfInvalidRow
*/
private boolean readSingleCell = false;
需要说明的是titleRows默认是0,如果我们没有标题行的话就不要去设置这个值,这里的标题不是表头 情形一:
在这里插入图片描述 如上,这个Excel没有标题,其导入的话就不需要设置titleRows的值,headRows的值也可以直接用默认的。 情形二:
在这里插入图片描述 如上,这个Excel是有标题的,其导入时我们就需要设置titleRows和headRows的值,如下标题行设置为1,表头行设置为2。
代码语言:javascript复制 ImportParams params = new ImportParams();
params.setTitleRows(1);
params.setHeadRows(2);
2. Excel导入校验
对象 EasyPoi的校验使用也很简单,对象上加上通用的校验规则或者这定义的这个看你用的哪个实现 然后params.setNeedVerfiy(true);配置下需要校验就可以了 看下具体的代码
代码语言:javascript复制/**
* 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;
使用方式
代码语言:javascript复制@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;
/**
* 数据源
*/
private Workbook workbook;
一个是集合,是一个是是否有校验失败的数据,一个原本的文档,但是在文档后面追加了错误信息
注意,这里的list,有两种返回
一种是只返回正确的数据 一种是返回全部的数据,但是要求这个对象必须实现IExcelModel接口,如下 IExcelModel
代码语言:javascript复制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 获取错误数据的行号
代码语言:javascript复制public interface IExcelDataModel {
/**
* 获取行号
* @return
*/
public int getRowNum();
/**
* 设置行号
* @param rowNum
*/
public void setRowNum(int rowNum);
}
需要对象实现这个接口
每行的错误数据也会填到这个错误信息中,方便用户后面自定义处理 看下代码
代码语言:javascript复制 @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时,我们需要统一Excel的字体的大小,字型等。我们可以通过实现IExcelExportStyler接口或者继承ExcelExportStylerDefaultImpl类来实现。如下所示:我们定义了一个ExcelStyleUtil工具类继承了ExcelExportStylerDefaultImpl(样式的默认实现类),并且将列头,标题,单元格的字体都设置为了宋体。
代码语言:javascript复制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类进行包装,如下所示:
代码语言:javascript复制public class OfficeExportUtil {
/**
*
*/
private static final Integer EXPORT_EXCEL_MAX_NUM = 20000;
/**
* 获取导出的Workbook对象
*
* @param sheetName 页签名
* @param clazz 类对象
* @param list 导出的数据集合
* @return
* @author xiagwei
* @date 2020/2/10 4:45 PM
*/
public static Workbook getWorkbook(String sheetName, Class clazz, List<?> list) {
//判断数据是否为空
if (CollectionUtils.isEmpty(list)) {
log.info("***********导出数据行数为空!");
list = new ArrayList<>();
}
if (list.size() > EXPORT_EXCEL_MAX_NUM) {
log.info("***********导出数据行数超过:" EXPORT_EXCEL_MAX_NUM "条,无法导出、请添加导出条件!");
list = new ArrayList<>();
}
log.info("***********" sheetName "的导出数据行数为" list.size() "");
//获取导出参数
ExportParams exportParams = new ExportParams();
//设置导出样式
exportParams.setStyle(ExcelStyleUtil.class);
//设置sheetName
exportParams.setSheetName(sheetName);
//输出workbook流
return ExcelExportUtil.exportExcel(exportParams, clazz, list);
}
使用的话,我们只需要获取需要导出的数据,然后调用OfficeExportUtil的getWorkbook方法。
总结
本文主要介绍了easypoi的使用和相关属性,easypoi使用起来还是蛮简单的。但是有个缺点是导入导出大批量数据时性能没那么好。
参考代码
https://gitee.com/lemur/easypoi-test
参考
EasyPoi教程