工作中需要不少的Excel表格的导入导出,今天就给大家安利一个Excel导入比较好用的工具类easypoi,因为是中国人编写的所有里面的注释都是中文的,对我这种英语不好的人非常友好。能够支持使用注解导入导出,复杂的导入检验,模板导出等功能
该工具类的官方文档
easypoi :http://doc.wupaas.com/docs/easypoi/easypoi-1c0u6ksp2r091
easypoi依赖引入
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>3.2.0</version>
</dependency>
easypoi之Excel导入
首先我们创建个实体类,在每个字段上加上相对应的注解 @Excel
@Excel(name = "姓名")
private String name;
@Excel(name = "性别", replace = {"男_0", "女_1"})
private String sex;
@Excel(name = "年龄", fixedIndex = 2)
private Integer age;
@Excel常用值解释
name 对应是Excel每一列的列明
replace 可以进行值的替换 如Excel中输入为男解析后可改变为0
fixedIndex 固定的某一列 可以解决name值不好解析的问题,默认从0开始
其他有需要的大家可以去官方文档或者进注解查看
Conroller层的代码也很简单
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile multipartFile) throws Exception {
ImportParams params = new ImportParams();
// 表头设置为1行
params.setHeadRows(1);
ExcelImportResult<ExcelUser> result = ExcelImportUtil.importExcelMore
(multipartFile.getInputStream(), ExcelUser.class, params);
System.out.println(result.getList().toString());
return null;
}
可以看到图片已经正常的被解析到 并且性别已经解析为我们定义的编码
导入之数据校验
因为excel表格都是给用户输入的,所以用户导入的表格有一些并不符合我们数据库存入的规则 所以我们要对用户导入的表格内容进行一些简单的数据校验
首先我们要创建一个检验类并且实现IExcelVerifyHandler接口重写该接口的verifyHandler方法在方法体里做具体的检验
@Component
public class UserVerifyHandler implements IExcelVerifyHandler<ExcelUser> {
// private final ThreadLocal<List<ExcelUser>> threadLocal = new ThreadLocal<>();
@Autowired
UserService userService;
@Override
public ExcelVerifyHandlerResult verifyHandler(ExcelUser excelProduct) {
StringJoiner joiner = new StringJoiner(",");
//检验名字是否存在
boolean flag = userService.checkout(excelProduct.getName());
if (flag) {
joiner.add("该名字已存在");
}
if (joiner.length() != 0) {
return new ExcelVerifyHandlerResult(false, joiner.toString());
}
return new ExcelVerifyHandlerResult(true);
}
这里我们实验检验名字是否已经在数据库重复
改造我们的实体类 实体类要实现IExcelDataModel IExcelModel这两个接口改造后的实体类是这个样子
@Data
public class ExcelUser implements IExcelDataModel, IExcelModel {
/**
* 行号
*/
private int rowNum;
/**
* 错误消息
*/
private String errorMsg;
@Excel(name = "姓名")
@NotBlank(message = "[姓名]不能为空")
private String name;
@Excel(name = "性别", replace = {"男_0", "女_1"})
@NotBlank(message = "[性别]不能为空")
@Pattern(regexp = "[01]", message = "性别输入错误")
private String sex;
@Excel(name = "年龄", fixedIndex = 2)
@NotBlank(message = "[年龄]不能为空")
private String age;
这里检验了哪些参数不能为空,rowNum代表的是第几行 errorMsg代表的是错误消息
需要注意的是加入校验时要把字段类型改为String
Controller层改造
@Autowired
private UserVerifyHandler userVerifyHandler;
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile multipartFile) throws Exception {
ImportParams params = new ImportParams();
// 表头设置为1行
params.setHeadRows(1);
// 标题行设置为0行,默认是0,可以不设置
params.setTitleRows(0);
//开启检验
params.setNeedVerfiy(true);
params.setVerifyHandler(userVerifyHandler);
ExcelImportResult<ExcelUser> result = ExcelImportUtil.importExcelMore
(multipartFile.getInputStream(), ExcelUser.class, params);
System.out.println(result.getFailList().toString());
System.out.println(result.getList().toString());
return null;
}
这里就改了三行代码 首先注入UserVerifyHandler 并且开启校验需要注意的是导入检验的错误数据会放到这个集合里result.getFailList()
params.setNeedVerfiy(true);
params.setVerifyHandler(userVerifyHandler);
可以看到,我们的校验是成功的一些不能校验通过的数据我们存放在了一个专门的集合里面,里面有我们设置的一些校验失败的一些提示语句,模仿数据库数据存在的校验也成功了因为并没有真正的去数据库查该名字是否存在,为了演示效果统一设置为存在了。并且在性别那里我们在也控制了只能输入男或女。
我们不允许数据库出现两个相同的名字 那么导入表格也不能存在相同的名字下面介绍怎么校验导入表格里校验数据是否重复
校验类更改
@Component
public class UserVerifyHandler implements IExcelVerifyHandler<ExcelUser> {
private final ThreadLocal<List<ExcelUser>> threadLocal = new ThreadLocal<>();
@Autowired
UserService userService;
@Override
public ExcelVerifyHandlerResult verifyHandler(ExcelUser excelProduct) {
StringJoiner joiner = new StringJoiner(",");
//检验名字是否存在
boolean flag = userService.checkout(excelProduct.getName());
if (flag) {
joiner.add("该名字已存在");
}
List<ExcelUser> threadLocalVal = threadLocal.get();
if (threadLocalVal == null) {
threadLocalVal = new ArrayList<>();
}
threadLocalVal.forEach(e -> {
if (e.getName().equals(excelProduct.getName())) {
int lineNumber = e.getRowNum() + 1;
joiner.add("名字与" + lineNumber + "行重复");
}
});
// 添加本行数据对象到ThreadLocal中
threadLocalVal.add(excelProduct);
threadLocal.set(threadLocalVal);
if (joiner.length() != 0) {
return new ExcelVerifyHandlerResult(false, joiner.toString());
}
if (joiner.length() != 0) {
return new ExcelVerifyHandlerResult(false, joiner.toString());
}
return new ExcelVerifyHandlerResult(true);
}
}
Controller更改
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile multipartFile) throws Exception {
ImportParams params = new ImportParams();
ExcelImportResult<ExcelUser> result;
try {
// 表头设置为1行
params.setHeadRows(1);
// 标题行设置为0行,默认是0,可以不设置
params.setTitleRows(0);
//开启检验
params.setNeedVerfiy(true);
params.setVerifyHandler(userVerifyHandler);
result = ExcelImportUtil.importExcelMore
(multipartFile.getInputStream(), ExcelUser.class, params);
} finally {
// 清除threadLocal 防止内存泄漏
ThreadLocal<List<ExcelUser>> threadLocal = userVerifyHandler.getThreadLocal();
if (threadLocal != null) {
threadLocal.remove();
}
}
System.out.println(result.getFailList().toString());
System.out.println(result.getList().toString());
return null;
}
threadLocal 一定要记得清除,否则可能造成内存泄漏
第二条错误数据中已经提示名字与第几行重复
上面的一些导入功能基本上已经能够满足大部分需求了,下面介绍一下导出功能
easypoi之Excel导出
@GetMapping("/getList")
public String getList(HttpServletRequest request, HttpServletResponse response) {
List<ExcelUser> list = new ArrayList<>();
ExcelUser excelUser = new ExcelUser();
excelUser.setName("张三");
excelUser.setAge("20");
excelUser.setSex("男");
ExcelUser excelUser1 = new ExcelUser();
excelUser1.setName("李四");
excelUser1.setAge("30");
excelUser1.setSex("女");
list.add(excelUser);
list.add(excelUser1);
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("计算机一班学生", "学生"),
ExcelUser.class, list);
// 判断数据
if(workbook == null) {
return "fail";
}
// 设置excel的文件名称
String excelName = "测试导出";
// 重置响应对象
response.reset();
// 指定下载的文件名--设置响应头
response.setHeader("Content-Disposition", "attachment;filename=" +excelName+".xls");
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
// 写出数据输出流到页面
try {
OutputStream output = response.getOutputStream();
BufferedOutputStream bufferedOutPut = new BufferedOutputStream(output);
workbook.write(bufferedOutPut);
bufferedOutPut.flush();
bufferedOutPut.close();
output.close();
} catch (IOException e) {
e.printStackTrace();
}
return "success";
}
easypoi还支持一对多的导出并且可以设置导出的Excel格式
导出改造
ExportParams exportParams = new ExportParams("计算机一班学生", "学生");
exportParams.setStyle(ExcelStyleUtil.class);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams,
ExcelUser.class, list);
ExcelStyleUtil 需要继承IExcelExportStyler接口,代码如下可以根据自己需求进行修改
public class ExcelStyleUtil implements IExcelExportStyler {
private static final short STRING_FORMAT = (short) BuiltinFormats.getBuiltinFormat("TEXT");
private static final short FONT_SIZE_TEN = 9;
private static final short FONT_SIZE_ELEVEN = 10;
private static final short FONT_SIZE_TWELVE = 10;
/**
* 大标题样式
*/
private CellStyle headerStyle;
/**
* 每列标题样式
*/
private CellStyle titleStyle;
/**
* 数据行样式
*/
private CellStyle styles;
public ExcelStyleUtil(Workbook workbook) {
this.init(workbook);
}
/**
* 初始化样式
*
* @param workbook
*/
private void init(Workbook workbook) {
this.headerStyle = initHeaderStyle(workbook);
this.titleStyle = initTitleStyle(workbook);
this.styles = initStyles(workbook);
}
/**
* 大标题样式
*
* @param color
* @return
*/
@Override
public CellStyle getHeaderStyle(short color) {
return headerStyle;
}
/**
* 每列标题样式
*
* @param color
* @return
*/
@Override
public CellStyle getTitleStyle(short color) {
return titleStyle;
}
/**
* 数据行样式
*
* @param parity 可以用来表示奇偶行
* @param entity 数据内容
* @return 样式
*/
@Override
public CellStyle getStyles(boolean parity, ExcelExportEntity entity) {
return styles;
}
/**
* 获取样式方法
*
* @param dataRow 数据行
* @param obj 对象
* @param data 数据
*/
@Override
public CellStyle getStyles(Cell cell, int dataRow, ExcelExportEntity entity, Object obj, Object data) {
return getStyles(true, entity);
}
/**
* 模板使用的样式设置
*/
@Override
public CellStyle getTemplateStyles(boolean isSingle, ExcelForEachParams excelForEachParams) {
return null;
}
/**
* 初始化--大标题样式
*
* @param workbook
* @return
*/
private CellStyle initHeaderStyle(Workbook workbook) {
CellStyle style = getBaseCellStyle(workbook);
style.setFont(getFont(workbook, FONT_SIZE_TWELVE, true));
return style;
}
/**
* 初始化--每列标题样式
*
* @param workbook
* @return
*/
private CellStyle initTitleStyle(Workbook workbook) {
CellStyle style = getBaseCellStyle(workbook);
style.setFont(getFont(workbook, FONT_SIZE_ELEVEN, false));
//背景色
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
return style;
}
/**
* 初始化--数据行样式
*
* @param workbook
* @return
*/
private CellStyle initStyles(Workbook workbook) {
CellStyle style = getBaseCellStyle(workbook);
style.setFont(getFont(workbook, FONT_SIZE_TEN, false));
style.setDataFormat(STRING_FORMAT);
return style;
}
/**
* 基础样式
*
* @return
*/
private CellStyle getBaseCellStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
//下边框
style.setBorderBottom(BorderStyle.THIN);
//左边框
style.setBorderLeft(BorderStyle.THIN);
//上边框
style.setBorderTop(BorderStyle.THIN);
//右边框
style.setBorderRight(BorderStyle.THIN);
//水平居中
style.setAlignment(HorizontalAlignment.CENTER);
//上下居中
style.setVerticalAlignment(VerticalAlignment.CENTER);
//设置自动换行
style.setWrapText(true);
return style;
}
/**
* 字体样式
*
* @param size 字体大小
* @param isBold 是否加粗
* @return
*/
private Font getFont(Workbook workbook, short size, boolean isBold) {
Font font = workbook.createFont();
//字体样式
font.setFontName("宋体");
//是否加粗
font.setBold(isBold);
//字体大小
font.setFontHeightInPoints(size);
return font;
}
因为导出比较简单而且官方文档写的也比较详细,这里不做过多赘诉,大家有其他导出需求的可以参考官方文档。