Easypoi包的使用
前言
最近老板让我实现一个导入导出的功能,这个项目因为是一个很老的维护项目,关于excel的导入导出用的还是 jexcelapi这个包,我虽然已经封装了工具类了,但是我还是感觉很麻烦~ ,尤其是标题,不停的add(菜鸡的我)
最近,看了看一些技术博客,正好看到了关于文件导入导出好用的包,EasyPoi 与 EasyExcel。恩,看这名字,咱来看看用这两个有多Easy~~
首先说一下这两个包的区别:
- EasyPoi 占用内存大,性能不够好(看和谁比)。这个在学习得时候看到官网上有很多人评论说了对应得问题,但是把,我还是觉得比 jexcelapi好用~~
- EasyExcel 是一款阿里开源,大厂保证,应该性能没得说把(狗头保命)官话:具有处理快速、占用内存小、使用方便的特点,在Github上已有
22k+
Star,可非常流行。
这篇主要来讲解我学习的关于 Easypoi 包中的知识,EasyExcel 下篇博客安排上,我先去玩玩~~
好了,回归正题,咱们来看看 Easypoi 是个啥子,怎么玩吧~
官话: easypoi功能如同名字easy,主打的功能就是容易,让一个没见接触过poi的人员 就可以方便的写出Excel导出,Excel模板导出,Excel导入,Word模板导出,通过简单的注解和模板 语言(熟悉的表达式语法),完成以前复杂的写法。
人话:会加@注解就能使用这个包就会导入导出了,是不是特别easy。
特点:
- 设计精巧,来加注解,调用一些工具类,就可以使用,使用简单
- 提供了很多接口,扩展很简单
- 搞了很多的默认值,这样的话,兜底不怕了~
- 支撑SpringMVC、SpringBoot
功能
Excel自适应xls和xlsx两种格式,word只支持docx模式
-
Excel导入
-
注解导入
-
Map导入
-
大数据量导入sax模式
-
导入文件保存
-
文件校验
-
字段校验
-
-
Excel导出
- 注解导出
- 模板导出
- html导出
-
Excel转html
-
Word导出
-
PDF导出
官方列举这么多功能,到底有没有这么厉害的,咱们来一点点玩玩~
使用
像这种第三方的包,使用就分三步走:导包、官网举例参考使用、验证。具体的原理有兴趣的自己去探寻探寻,有空我学习学习后再来叨叨~
1、导包
在项目的pom.xml 文件夹中导入包。
这里有两种导入方式,分为SpringBoot项目与SpringMvc项目,如果你简单的使用excel导入导出功能的话,这个包够用了。
下面设计到的源码也是针对 4.3.0 这个版本来的,其他版本可以看对应的源码来~
// SpringBoot项目中导入,这个包里面包含了下面的几个包
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>
// SpringMvc项目中导入
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId> //导入导出的工具包,可以完成Excel导出,导入,Word的导出,Excel的导出功能
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId> //耦合了spring-mvc 基于AbstractView,极大的简化spring-mvc下的导出功能
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId> //基础注解包,作用与实体对象上,拆分后方便maven多工程的依赖管理
<version>4.3.0</version>
</dependency>
如果你是需要校验、PDF导出、Word导出、 sax ,自己根据需求加吧~
<!-- sax 读取时候用到的 -->
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>${xerces.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>${poi.version}</version>
<optional>true</optional>
</dependency>
<!-- Word 需要使用 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.3</version>
<optional>true</optional>
</dependency>
<!-- 校验,下面两个实现 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.bval</groupId>
<artifactId>org.apache.bval.bundle</artifactId>
<version>1.1.0</version>
</dependency>
<!-- PDF -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.6</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
<optional>true</optional>
</dependency>
2、注解说明
这个包的注解其实挺少的,有六个,@CelleStyle没啥东西,我主要介绍其他五个把:
注解 | 使用位置 | 描述 |
---|---|---|
Excel | 只适用在 属性FIELD 上 | Excel中的一列 |
ExcelCollection | 只适用在 属性FIELD 上 | 表示一个集合,主要针对一对多的导出 |
ExcelEntity | 只适用在 属性FIELD 上 | 表示一个继续深入导出的实体 说明这个Feild是一个实体类,里面的每个属性都要导出 |
ExcelIgnore | 只适用在 属性FIELD 上 | 导出的时候跳过这个Fileld |
ExcelTarget | 适用在 类 接口 枚举 上 | 放在最外层 描述这个对象的id,以便支持一个对象可以针对不同导出做出不同处理 |
2.1 注解基础知识的介绍
// 表明注解可以使用在哪里
// 这里表示注解可以使用在 方法 属性 注解 构造方法 参数 type
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
//描述保留注解的各种策略,它们与元注解(@Retention)一起指定注释要保留多长时间
@Retention(RetentionPolicy.RUNTIME)
//表明这个注解是由 javadoc记录的
@Documented
public @interface MyOwnAnnotation {
}
2.2 @Excel
@Excel
注解一般我们标记在FILED上,表示这个FILED是Excel中的一列,一般都靠它实现,对应字段及其解释如下(看了一下,可实现功能还是挺多的)
注解源码:
package cn.afterturn.easypoi.excel.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Excel {
// 列名,支持name_id,必须给定
String name();
// 导出时间设置,如果字段是Date类型则不需要设置
// 数据库如果是string 类型,这个需要设置这个数据库格式,用以转换时间格式输出
// 默认yyyyMMddHHmmss
String databaseFormat() default "yyyyMMddHHmmss";
// 导出的时间格式
// 如果这个值为空,就不需要格式化日期
// 如果不为空,则需要格式化日期
String exportFormat() default "";
// 导入的时间格式
// 如果这个值为空,就不需要格式化日期
// 如果不为空,则需要格式化日期
String importFormat() default "";
//时间格式,相当于同时设置了exportFormat 和 importFormat
String format() default "";
// 列的排序
String orderNum() default "0";
// 值的替换 导出是{a_1,b_2} 导入反过来
// 就是当值为1的时候,替换为1,值为2的时候,替换为b
String[] replace() default {};
// 数字格式化,参数是Pattern,使用的对象是DecimalFormat
String numFormat() default "";
// 文字后缀,如% 90 变成90%,在导出该值的后面加上suffix指定的值
String suffix() default "";
// 单元格宽度
double width() default 10.0D;
// 时区,GMT zone的Zone ID,具体取值可查看java.util.ZoneId里面的可选值
String timezone() default "";
/** @deprecated */
@Deprecated
double height() default 10.0D;
// 导出类型
// 1 从file读取 2 是从数据库中读取
// 默认是 1 从文件读取 同样导入也是一样的
int imageType() default 1;
// 是否换行 即支持\n
// 默认支撑
boolean isWrap() default true;
// 是否需要纵向合并单元格(用于含有list中,单个的单元格,合并list创建的多个row)
// 值为合并的单元格的row数组
int[] mergeRely() default {};
// 是否需要纵向合并单元格(用于含有list中,单个的单元格,合并list创建的多个row)
// 如果需要合并,需要指定mergeRely
boolean needMerge() default false;
// 是否纵向合并内容相同的单元格
// 默认false 不合并
boolean mergeVertical() default false;
// 组名称,这个对应 @ExcelEntity 的name,用于导出的不同处理
String groupName() default "";
// 字典名称
String dict() default "";
// 是否插入下拉
boolean addressList() default false;
// 保存图片的地址
String savePath() default "/excel/upload/img";
// 单元格的类型,对应的type,默认为1
// STRING_TYPE = 1; string类型
// IMAGE_TYPE = 2; 图片类型
// FUNCTION_TYPE = 3; 函数
// DATE_TYPE = 4; 日期
// DOUBLE_TYPE = 10; double精度
int type() default 1;
// 是否是统计
boolean isStatistics() default false;
// 这个是不是超链接,如果是需要实现接口返回对象
boolean isHyperlink() default false;
// 导入校验字段,默认为false,就行无需校验
String isImportField() default "false";
// 固定的列
int fixedIndex() default -1;
// 是否隐藏列
boolean isColumnHidden() default false;
// 枚举导出属性字段
String enumExportField() default "";
// 枚举导入静态方法
String enumImportMethod() default "";
// 脱敏规则
String desensitizationRule() default "";
}
2.2 @ExcelCollection
@ExcelCollection
注解一般我们标记在FILED上,表示一个继续深入导出的实体,说明这个Feild是一个实体类,里面的每个属性都要针对@Excel标记的进行导出
注解源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ExcelCollection {
// 列名称,必不可少
String name();
// name如果不指定,支持name_id (如果标注ExcelTarget,这个id可以为其value)
String id() default "";
// 排序号
String orderNum() default "0";
// 默认该字段是ArrayList,可指定该字段Collection类型
Class<?> type() default ArrayList.class;
}
2.3 @ExcelEntity
@ExcelEntity
注解一般我们标记在FILED上
注解源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ExcelEntity {
//列的名称,支持name_id(如果标注ExcelTarget,这个id可以为其value)
String id() default "";
// 列名称,如果show()为true,一定要指定
String name() default "";
// 是否要展示这个filed,如果为true的话,name一定要指定
boolean show() default false;
}
2.4 @ExcelIgnore
@ExcelIgnore
注解一般我们标记在FILED上,用于是否忽略,忽略的属性标注 @ExcelIgnore 即可
注解源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ExcelIgnore {
}
2.5 @ExcelTarget
@ExcelTarget
注解一般我们标记在类 接口 枚举 上,放在最外层,描述这个对象的id,以便支持一个对象可以针对不同导出做出不同处理
注解源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ExcelTarget {
// 标注是哪个导出,@Excel的name可以为该值
String value();
}
3、工具类说明
3.1 ExportParams
导出参数,主要来定义 Excel的一些信息,在ExcelExportUtil 里面进行文件导出的,都是使用这个参数来作为导出参数。
该类涉及的字段与含义如下,仅仅提供了几个构造函数:
- 设置 表格第一行title 、 表格第二行子title、sheet名称
- 设置 表格第一行title 、 sheet名称
- 设置 表格第一行title 、 sheet名称、导出文件的格式(默认xls)
/**
* Excel 导出参数
*/
@Data
public class ExportParams extends ExcelBaseParams {
/**
* 表格第一行title名称
*/
private String title;
/**
* 表格第一行title高度
*/
private short titleHeight = 10;
/**
* 表格第一行子title名称
*/
private String secondTitle;
/**
* 表格第一行子title高度
*/
private short secondTitleHeight = 8;
/**
* sheetName
*/
private String sheetName;
/**
* 过滤的属性
*/
private String[] exclusions;
/**
* 是否添加index
*/
private boolean addIndex;
/**
* index名称
*/
private String indexName = "序号";
/**
* 冰冻列
*/
private int freezeCol;
/**
* 表头颜色 & 标题颜色
*/
private short color = HSSFColor.HSSFColorPredefined.WHITE.getIndex();
/**
* 第二行标题颜色
* 属性说明行的颜色 例如:HSSFColor.SKY_BLUE.index 默认
*/
private short headerColor = HSSFColor.HSSFColorPredefined.SKY_BLUE.getIndex();
/**
* Excel 导出版本
*/
private ExcelType type = ExcelType.HSSF;
/**
* Excel 导出style
*/
private Class<?> style = ExcelExportStylerDefaultImpl.class;
/**
* 表头高度
*/
private double headerHeight = 9D;
/**
* 是否创建表头
*/
private boolean isCreateHeadRows = true;
/**
* 是否动态获取数据
*/
private boolean isDynamicData = false;
/**
* 是否追加图形
*/
private boolean isAppendGraph = true;
/**
* 是否固定表头
*/
private boolean isFixedTitle = true;
/**
* 单sheet最大值
* 03版本默认6W行,07默认100W
*/
private int maxNum = 0;
/**
* 导出时在excel中每个列的高度 单位为字符,一个汉字=2个字符
* 全局设置,优先使用
*/
private short height = 0;
/**
* 只读
*/
private boolean readonly = false;
/**
* 列宽自适应,如果没有设置width 也自适应宽度
*/
private boolean autoSize = false;
// 构造函数
public ExportParams() {
}
public ExportParams(String title, String sheetName) {
this.title = title;
this.sheetName = sheetName;
}
public ExportParams(String title, String sheetName, ExcelType type) {
this.title = title;
this.sheetName = sheetName;
this.type = type;
}
public ExportParams(String title, String secondTitle, String sheetName) {
this.title = title;
this.secondTitle = secondTitle;
this.sheetName = sheetName;
}
public short getSecondTitleHeight() {
return (short) (secondTitleHeight * 50);
}
public short getTitleHeight() {
return (short) (titleHeight * 50);
}
public short getHeight() {
return height == -1 ? -1 : (short) (height * 50);
}
public short getHeaderHeight() {
return (short) (titleHeight * 50);
}
}
3.2 ExcelExportUtil
导出工具类。构造导出需要的参数ExportParams,用该类中的静态方法进行导出指定excel文件
源码如下,对应的方法与参数含义如下:(具体可以看看下面的使用范例)
/**
* excel 导出工具类
*/
public final class ExcelExportUtil {
public static int USE_SXSSF_LIMIT = 1000000;
public static final String SHEET_NAME = "sheetName";
private ExcelExportUtil() {
}
/**
* 大数据量导出
*
* @param entity 表格标题属性
* @param pojoClass Excel对象Class
*/
public static IWriter<Workbook> exportBigExcel(ExportParams entity, Class<?> pojoClass) {
ExcelBatchExportService batchServer = new ExcelBatchExportService();
batchServer.init(entity, pojoClass);
return batchServer;
}
/**
* 大数据量导出
*
* @param entity 表格标题属性
* @param excelParams cell映射类参数list
* @return
*/
public static IWriter<Workbook> exportBigExcel(ExportParams entity, List<ExcelExportEntity> excelParams) {
ExcelBatchExportService batchServer = new ExcelBatchExportService();
batchServer.init(entity, excelParams);
return batchServer;
}
/**
* 大数据量导出
*
* @param entity 表格标题属性
* @param pojoClass Excel对象Class
* @param server 查询数据的接口
* @param queryParams 查询数据的参数
*/
public static Workbook exportBigExcel(ExportParams entity, Class<?> pojoClass,
IExcelExportServer server, Object queryParams) {
ExcelBatchExportService batchServer = new ExcelBatchExportService();
batchServer.init(entity, pojoClass);
return batchServer.exportBigExcel(server, queryParams);
}
/**
* 大数据量导出
* @param entity 表格标题属性
* @param excelParams 表格标题属性
* @param server 查询数据的接口
* @param queryParams 查询数据的参数
* @return
*/
public static Workbook exportBigExcel(ExportParams entity, List<ExcelExportEntity> excelParams,
IExcelExportServer server, Object queryParams) {
ExcelBatchExportService batchServer = new ExcelBatchExportService();
batchServer.init(entity, excelParams);
return batchServer.exportBigExcel(server, queryParams);
}
/**
* @param entity 表格标题属性
* @param pojoClass Excel对象Class
* @param dataSet Excel对象数据List
*/
public static Workbook exportExcel(ExportParams entity, Class<?> pojoClass,
Collection<?> dataSet) {
Workbook workbook = getWorkbook(entity.getType(), dataSet.size());
new ExcelExportService().createSheet(workbook, entity, pojoClass, dataSet);
return workbook;
}
private static Workbook getWorkbook(ExcelType type, int size) {
if (ExcelType.HSSF.equals(type)) {
return new HSSFWorkbook();
} else {
return new XSSFWorkbook();
}
}
/**
* 根据Map创建对应的Excel
*
* @param entity 表格标题属性
* @param entityList Map对象列表
* @param dataSet Excel对象数据List
*/
public static Workbook exportExcel(ExportParams entity, List<ExcelExportEntity> entityList,
Collection<?> dataSet) {
Workbook workbook = getWorkbook(entity.getType(), dataSet.size());
;
new ExcelExportService().createSheetForMap(workbook, entity, entityList, dataSet);
return workbook;
}
/**
* 根据Map创建对应的Excel(一个excel 创建多个sheet)
*
* @param list 多个Map key title 对应表格Title key entity 对应表格对应实体 key data
* Collection 数据
*/
public static Workbook exportExcel(List<Map<String, Object>> list, ExcelType type) {
Workbook workbook = getWorkbook(type, 0);
for (Map<String, Object> map : list) {
ExcelExportService service = new ExcelExportService();
ExportParams params = (ExportParams) map.get("title");
params.setType(type);
service.createSheet(workbook,params,
(Class<?>) map.get("entity"), (Collection<?>) map.get("data"));
}
return workbook;
}
/**
* 导出文件通过模板解析,不推荐这个了,推荐全部通过模板来执行处理
* @param params 导出参数类
* @param pojoClass 对应实体
* @param dataSet 实体集合
* @param map 模板集合
* @return
*/
@Deprecated
public static Workbook exportExcel(TemplateExportParams params, Class<?> pojoClass,
Collection<?> dataSet, Map<String, Object> map) {
return new ExcelExportOfTemplateUtil().createExcelByTemplate(params, pojoClass, dataSet,
map);
}
/**
* 导出文件通过模板解析只有模板,没有集合
*
* @param params 导出参数类
* @param map 模板集合
* @return
*/
public static Workbook exportExcel(TemplateExportParams params, Map<String, Object> map) {
return new ExcelExportOfTemplateUtil().createExcelByTemplate(params, null, null, map);
}
/**
* 导出文件通过模板解析只有模板,没有集合
* 每个sheet对应一个map,导出到处,key是sheet的NUM
*
* @param params 导出参数类
* @param map 模板集合
* @return
*/
public static Workbook exportExcel(Map<Integer, Map<String, Object>> map,
TemplateExportParams params) {
return new ExcelExportOfTemplateUtil().createExcelByTemplate(params, map);
}
/**
* 导出文件通过模板解析只有模板,没有集合
* 每个sheet对应一个list,按照数量进行导出排序,key是sheet的NUM
*
* @param params 导出参数类
* @param map 模板集合
* @return
*/
public static Workbook exportExcelClone(Map<Integer, List<Map<String, Object>>> map,
TemplateExportParams params) {
return new ExcelExportOfTemplateUtil().createExcelCloneByTemplate(params, map);
}
}
3.4 ImportParams
导入的参数设置,主要根据你文件里面的内容的格式来进行设置
/**
* 导入参数设置
*/
@Data
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 boolean verifyFileSplit = true;
/**
* 校验处理接口
*/
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;
/**
* 是否并行计算
*/
private boolean concurrentTask = false;
/**
* 最小截取大小
*/
private Integer critical = 1000;
}
3.3 ExcelImportUtil
导入工具。传入之地那个的文件、导出的entity的类,对应的import的参数来进行文件的导入。
/**
* Excel 导入工具
*/
@SuppressWarnings({ "unchecked" })
public class ExcelImportUtil {
private ExcelImportUtil() {
}
private static final Logger LOGGER = LoggerFactory.getLogger(ExcelImportUtil.class);
/**
* Excel 导入 数据源本地文件,不返回校验结果 导入 字 段类型 Integer,Long,Double,Date,String,Boolean
*
* @param file 导入的文件
* @param pojoClass 需要导入数据成的entity类
* @param params 导入的参数
* @return
*/
public static <T> List<T> importExcel(File file, Class<?> pojoClass, ImportParams params) {
FileInputStream in = null;
try {
in = new FileInputStream(file);
return new ExcelImportService().importExcelByIs(in, pojoClass, params, false).getList();
} catch (ExcelImportException e) {
throw new ExcelImportException(e.getType(), e);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
throw new ExcelImportException(e.getMessage(), e);
} finally {
IOUtils.closeQuietly(in);
}
}
/**
* Excel 导入 数据源IO流,不返回校验结果 导入 字段类型 Integer,Long,Double,Date,String,Boolean
*
* @param inputstream 文件流
* @param pojoClass 需要导入数据成的entity类
* @param params 导入的参数
*/
public static <T> List<T> importExcel(InputStream inputstream, Class<?> pojoClass,
ImportParams params) throws Exception {
return new ExcelImportService().importExcelByIs(inputstream, pojoClass, params, false).getList();
}
/**
* Excel 导入 数据源IO流 字段类型 Integer,Long,Double,Date,String,Boolean
* 支持校验,支持Key-Value
*
* @param inputstream 文件流
* @param pojoClass 需要导入数据成的entity类
* @param params 导入的参数
*/
public static <T> ExcelImportResult<T> importExcelMore(InputStream inputstream,
Class<?> pojoClass,
ImportParams params) throws Exception {
return new ExcelImportService().importExcelByIs(inputstream, pojoClass, params, true);
}
/**
* Excel 导入 数据源本地文件 字段类型 Integer,Long,Double,Date,String,Boolean
* 支持校验,支持Key-Value
* @param inputstream 文件流
* @param pojoClass 需要导入数据成的entity类
* @param params 导入的参数
*/
public static <T> ExcelImportResult<T> importExcelMore(File file, Class<?> pojoClass,
ImportParams params) {
FileInputStream in = null;
try {
in = new FileInputStream(file);
return new ExcelImportService().importExcelByIs(in, pojoClass, params, true);
} catch (ExcelImportException e) {
throw new ExcelImportException(e.getType(), e);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
throw new ExcelImportException(e.getMessage(), e);
} finally {
IOUtils.closeQuietly(in);
}
}
/**
* Excel 通过SAX解析方法,适合大数据导入,不支持图片
* 导入 数据源本地文件,不返回校验结果 导入字段类型 Integer,Long,Double,Date,String,Boolean
*
* @param inputstream 文件流
* @param pojoClass 需要导入数据成的entity类
* @param params 导入的参数
* @param handler 接口自定义处理类,用来解析对象
*/
public static void importExcelBySax(InputStream inputstream, Class<?> pojoClass,
ImportParams params, IReadHandler handler) {
new SaxReadExcel().readExcel(inputstream, pojoClass, params, handler);
}
}
4 简单使用
我们主要对上面的注解的属性来试试,给一个很直观的感受,到底怎么用
这里也仅仅测试一些我觉得可能经常用到的属性,复杂的一些属性,需要自己遇到之后再来记录在这里啦(后续更新)~
4.1 @Entity 使用一:利用简单属性导出excel
主要对以下属性来使用一下:
- name 、 databaseFormat、format(exportFormat、importFormat)、orderNum、replace、numFormat、suffix、width
再来回顾一下这几个属性干嘛的:
// 列名,支持name_id,必须给定
String name();
// 导出时间设置,如果字段是Date类型则不需要设置
// 数据库如果是string 类型,这个需要设置这个数据库格式,用以转换时间格式输出
// 默认yyyyMMddHHmmss
String databaseFormat() default "yyyyMMddHHmmss";
// 导出的时间格式
// 如果这个值为空,就不需要格式化日期
// 如果不为空,则需要格式化日期
String exportFormat() default "";
// 导入的时间格式
// 如果这个值为空,就不需要格式化日期
// 如果不为空,则需要格式化日期
String importFormat() default "";
//时间格式,相当于同时设置了exportFormat 和 importFormat
String format() default "";
// 列的排序
String orderNum() default "0";
// 值的替换 导出是{a_1,b_2} 导入反过来
// 就是当值为1的时候,替换为1,值为2的时候,替换为b
String[] replace() default {};
// 数字格式化,参数是Pattern,使用的对象是DecimalFormat
String numFormat() default "";
// 文字后缀,如% 90 变成90%,在导出该值的后面加上suffix指定的值
String suffix() default "";
// 单元格宽度,默认是10
double width() default 10.0D;
(1)定义entity类
主要分别对一些属性来定义了一些字段,在输出文件的时候,可以来对应看属性是否生效,从而来确定属性的使用
package com.study.pojo;
import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
@Getter
@Setter
@Builder
public class EasyPoiStudy {
/**
* id:没有加excel,不导出
*/
private Integer id;
/**
* 学生姓名
* 测试 name 与 width
*/
@Excel(name = "学生姓名", width = 30)
private String name;
/**
* 学生性别,该字段的取值为 1 / 2
* 那么我们文件输出的时候,就对应男与女
* suffix为后面加上一个生,则值就必须为男生/女生
* 测试 replace 、 suffix
*/
@Excel(name = "学生性别", replace = {"男_1", "女_2"}, suffix = "生")
private int sex;
/**
* 出生日期
* 测试 format 、 orderNum(其他默认都是0,这个为2肯定在所有列的后面)
*/
@Excel(name = "出生日期", orderNum = "2", format = "yyyy-MM-dd", width = 20)
private Date birthday;
/**
* 学生的身高
* 测试 numFormat
*/
@Excel(name = "学生的身高", numFormat = "#.0")
private Double height;
}
(2)执行类
我们定义了两个entity,数据分为为
- EasyPoiStudy(id=1, name=zhangsan, sex=1, birthday=Tue Jan 11 17:42:17 CST 2022, height=175.0)
- EasyPoiStudy(id=2, name=lisi, sex=2, birthday=Tue Jan 11 17:42:17 CST 2022, height=173.45)
public class EasyPoiStudyMain {
public static void main(String[] args) {
File file = new File("D:\\test.xls");
if (file.exists()) {
file.delete();
}
try {
FileOutputStream outputStream = new FileOutputStream(file);
List<EasyPoiStudy> list = new ArrayList<>();
EasyPoiStudy study_1 = EasyPoiStudy.builder().id(1).name("zhangsan").sex(1).birthday(new Date()).height(175.0).build();
EasyPoiStudy study_2 = EasyPoiStudy.builder().id(2).name("lisi").sex(2).birthday(new Date()).height(173.45).build();
list.add(study_1);
list.add(study_2);
ExportParams exportParams = new ExportParams("我的大标题", "我的子标题", "我的sheet");
Workbook workbook = ExcelExportUtil.exportExcel(exportParams,
EasyPoiStudy.class, list);
workbook.write(outputStream);
workbook.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
(3)验证
根据entity定义的属性,我们需要验证的点:
-
id是否没导出**(没加Excel的是不是就不导出)**
-
列名称**(name属性是否生效)**
-
列的宽度(默认宽度是10,name字段与birthday指定了宽度30与20,width属性是否生效)
-
性别是否是男生/女生**(replace 与 suffix 属性是否生效)**
-
出生日期的格式**(format属性是否生效)**
-
学生的身高的格式**(numFormat属性是否生效)**
excel截图:
4.2 使用二:利用简单属性导入excel
我们先改一下4.1导出的文件内容,改过后的文件内容截图如下:
(1)Entity:还是使用上面的那个,但是但是,(非常要注意,我们一定要有无参构造函数,这边类前面改一点)
@Getter
@Setter
//@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
// 或者直接标注@data
//@Data
public class EasyPoiStudy {
}
(2)执行类
package com.study.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.result.ExcelImportResult;
import com.study.pojo.EasyPoiStudy;
import org.apache.poi.ss.usermodel.Workbook;
import javax.xml.bind.SchemaOutputResolver;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
public class EasyPoiStudyMain {
public static void main(String[] args) {
File file = new File("D:\\test.xls");
ImportParams importParams = new ImportParams();
// 标题是从文件的哪一行开始的,是第三行,这里是从0开始数的,所以是2
importParams.setTitleRows(2);
List<EasyPoiStudy> list = ExcelImportUtil.importExcel(file, EasyPoiStudy.class, importParams);
System.out.println("list size : " + list.size());
list.forEach(System.out::println);
}
}
(3)看看结果
- EasyPoiStudy(name=zhangsan, sex=1, birthday=Tue Jan 11 12:00:00 CST 2022, height=175.02)
- EasyPoiStudy(name=lisi, sex=2, birthday=Tue Jan 11 00:00:00 CST 2022, height=173.4)
在验证的时候遇到了两个问题:
- 为什么我们定义的replace与suffix生效了,但是format 与 numformat没有生效?
- 为什么在类无无参构造函数的时候执行会报错?
说明:我在玩这个的时候,遇到了上面两个问题,我特别好奇,所以我下载了源码,调试了一下,才知道了为什么
1)针对第一个问题的源码
原因:我们获取单元格的时候,需要判断文件中单元格的格式,所以文件中的单元格格式一定要确认一下~
-
关于numformat的使用,必须单元格的格式是 CellType.NUMERIC
-
关于format的使用,必须单元格的格式不可以是 CellType.NUMERIC
if(cell instanceof Cell){
// 获取单元格的值
result = getCellValue(classFullName, (Cell) cell, entity);
}else{
result = cell;
}
if (entity != null) {
result = handlerSuffix(entity.getSuffix(), result);
result = replaceValue(entity.getReplace(), result);
result = replaceValue(entity.getReplace(), result);
if (dictHandler != null && StringUtils.isNoneBlank(entity.getDict())) {
result = dictHandler.toValue(entity.getDict(), object, entity.getName(), result);
}
}
/**
* 获取单元格内的值
*
* @param cell
* @param entity
* @return
*/
private Object getCellValue(String classFullName, Cell cell, ExcelImportEntity entity) {
if (cell == null) {
return "";
}
Object result = null;
// 判断字段的类型
if ("class java.util.Date".equals(classFullName)
|| "class java.sql.Date".equals(classFullName)
|| ("class java.sql.Time").equals(classFullName)
|| ("class java.time.Instant").equals(classFullName)
|| ("class java.time.LocalDate").equals(classFullName)
|| ("class java.time.LocalDateTime").equals(classFullName)
|| ("class java.sql.Timestamp").equals(classFullName)) {
//FIX: 单元格yyyyMMdd数字时候使用 cell.getDateCellValue() 解析出的日期错误
if (CellType.NUMERIC == cell.getCellType() && DateUtil.isCellDateFormatted(cell)) {
result = DateUtil.getJavaDate(cell.getNumericCellValue());
} else {
String val = "";
try {
val = cell.getStringCellValue();
} catch (Exception e) {
return null;
}
// 这里进行numberformat的处理
result = getDateData(entity, val);
if (result == null) {
return null;
}
}
if (("class java.time.Instant").equals(classFullName)) {
result = ((Date) result).toInstant();
} else if (("class java.time.LocalDate").equals(classFullName)) {
result = ((Date) result).toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
} else if (("class java.time.LocalDateTime").equals(classFullName)) {
result = ((Date) result).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
} else if (("class java.time.OffsetDateTime").equals(classFullName)) {
result = ((Date) result).toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime();
} else if (("class java.time.ZonedDateTime").equals(classFullName)) {
result = ((Date) result).toInstant().atZone(ZoneId.systemDefault());
} else if (("class java.sql.Date").equals(classFullName)) {
result = new java.sql.Date(((Date) result).getTime());
} else if (("class java.sql.Time").equals(classFullName)) {
result = new Time(((Date) result).getTime());
} else if (("class java.sql.Timestamp").equals(classFullName)) {
result = new Timestamp(((Date) result).getTime());
}
} else {
switch (cell.getCellType()) {
// 是文本的时候
case STRING:
result = cell.getRichStringCellValue() == null ? ""
: cell.getRichStringCellValue().getString();
break;
// 是NUMERIC的时候
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
if ("class java.lang.String".equals(classFullName)) {
result = formateDate(entity, cell.getDateCellValue());
}
} else {
// 如果是数值的时候,就根据entity来进行解析
result = readNumericCell(cell);
}
break;
case BOOLEAN:
result = Boolean.toString(cell.getBooleanCellValue());
break;
case BLANK:
break;
case ERROR:
break;
case FORMULA:
try {
result = readNumericCell(cell);
} catch (Exception e1) {
try {
result = cell.getRichStringCellValue() == null ? ""
: cell.getRichStringCellValue().getString();
} catch (Exception e2) {
throw new RuntimeException("获取公式类型的单元格失败", e2);
}
}
break;
default:
break;
}
}
return result;
}
2)针对第二个问题的源码如下:
在给对象赋值的时候,需要创建一个对象,一定需要无参构造函数 ,没有就会报错,那可能有人问了,我即使没定义,如果没有定义构造函数,不是会自带一个无参构造函数嘛,怎么会报错呢?
原因:因为在4.1的时候我们定义的entity,我使用了lombok的@Builder注解,这个注解会生成一个全参构造函数,那么就不会自动生成一个无参构造函数了,就需要自己手动加上无参构造函数 ,如果不知道lombok的使用,可以看我另外一篇博客: lombok 的使用
/**
* 彻底创建一个对象
*
* @param clazz
* @return
*/
public static Object createObject(Class<?> clazz, String targetId) {
Object obj = null;
if (clazz.equals(Map.class)) {
return new LinkedHashMap<String, Object>();
}
// clazz.newInstance(); 创建实例,这个必须要有无参构造函数
obj = clazz.newInstance();
Field[] fields = getClassFields(clazz);
for (Field field : fields) {
if (isNotUserExcelUserThis(null, field, targetId)) {
continue;
}
if (isCollection(field.getType())) {
ExcelCollection collection = field.getAnnotation(ExcelCollection.class);
PoiReflectorUtil.fromCache(clazz).setValue(obj, field.getName(),
collection.type().newInstance());
} else if (!isJavaClass(field) && !field.getType().isEnum()) {
PoiReflectorUtil.fromCache(clazz).setValue(obj, field.getName(),
createObject(field.getType(), targetId));
}
}
return obj;
}
总结
Easypoi 总体来说我觉得看它的源码难度不是很大,可能就是它想做的事情太多了,提供的扩展字段有点多,而且很多字段的描述也不是特别清楚,我在上面的学习过程中有的东西还是去看了源码才知道是啥意思、要怎么用~
这里简单说了导入导出的简单用法