一、springMVC对于easyPOI的支持
咱们现在开发前端都是直接使用SpringMVC,因此,EasyPoi也直接提供了对SpringMVC的支持!
要想操作excel表格的导入导出,只需要引入easypoi就可以简单的操作
以下为官方的介绍:
easypoi view 项目是为了更简单的方便搭建在导出时候的操作,利用spring mvc 的view 封装,更加符合spring mvc的风格 view下面包括多个 view的实现
EasypoiBigExcelExportView 大=数据量导出
EasypoiMapExcelView= map 列表导出
EasypoiPDFTemplateV=iew pdf导出
EasypoiSingleE=xcelView 注解导出
EasypoiTemplateExcelView 模板导出
EasypoiTemplateWordView word模板导出
MapGraphExcelView 图表导出
view的是使用方法大同小异,都有一个对应的bean,里面保护指定的参数常量 同意用modelmap.put(‘常量参数名’,‘值’)就可以,最后返回这个view名字
注解目录扫描的时候加上 cn.afterturn.easypoi.view 就可以使用了
二、使用方式
导入jar包
<!-- easypoi的支持 -->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>3.2.0</version>
</dependency>
三、导出
在浏览器点击导出从数据库导出数据
实体类employee,department
在需要导出的字段上打上注解
一些注解的语法:
1.年龄_emp
必需保证类的ExcelTarget的id是emp才会展示
2.format = “yyyy-MM-dd”:日期的格式
注:如果数据库是varchar,还需要配置databaseFormat
3.replace={“男_true”,“女_false”}
如果值是true,那么展示男,如果是false,则在页面展示女
4.@ExcelEntity(id=“emp”):对应另一个关连对象
id是为这个实体取一个名称,和关连的导出对应
5.type = 2 :代表这是一个图片展示
6.如果需要在指定表显示指定的字段需要给表取名字
然后想要的字段就在设置名字的时候加上别名,比如:
详细注解参考文档
@Entity
@Table(name="employee")
public class Employee extends BaseDomain {
@Excel(name = "用户名")
private String username;
private String password;
@Excel(name = "邮件",width = 25)
private String email;
@Excel(name = "年纪")
private Integer age;
@Excel(name = "头像",type = 2,width = 10 , height = 20)
//加入头像字段
private String headImage;
@ExcelEntity
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
@JsonIgnoreProperties(value={"hibernateLazyInitializer","handler","fieldHandler"})
private Department department;
省略getset...
}
1.准备导出页面,以实体类employee为例
在高级查询条中加入导出功能
<%--高级查询查询条--%>
<form id="querySearch" action="/employee/download">
用户名: <input name="username" class="easyui-textbox" style="width:80px">
邮件: <input name="email" class="easyui-textbox" style="width:80px">
部门:
<input id="cc" name="departmentId" class="easyui-combobox" panelHeight="auto"
data-options="valueField:'id',textField:'name',url:'/util/department'" />
<%--点击查询触发js 绑定函数 发送请求数据 高级查询条件封装成对象--%>
<a href="#" data-method="search" class="easyui-linkbutton" iconCls="icon-search">查询</a>
<button type="submit" class="easyui-linkbutton" iconCls="icon-search">导出</button>
</form>
为什么需要高级查询?
- 不是每次都要将所有的员工导出,所以是在高级查询的查询条加入导出按钮
- 点击导出会将查询条件也传到后台,后台按查询条件查出数据并且导出
点击导出
controller层
@RequestMapping("/download")
public String download(EmployeeQuery query,ModelMap map, HttpServletRequest request) {
List<Employee> list = employeeService.findByQuery(query);
//搞定路径问题
list.forEach(e -> {
String realPath = request.getServletContext().getRealPath("");
e.setHeadImage(realPath+e.getHeadImage());
});
ExportParams params = new ExportParams("员工数据", "测试", ExcelType.XSSF);
params.setFreezeCol(2); //设置固定列
map.put(NormalExcelConstants.DATA_LIST, list); // 数据集合
map.put(NormalExcelConstants.CLASS, Employee.class);//导出实体
map.put(NormalExcelConstants.PARAMS, params);//参数
map.put(NormalExcelConstants.FILE_NAME, "员工信息");//文件名称
return NormalExcelConstants.EASYPOI_EXCEL_VIEW;
}
点击导出报错
原因是配置了视图解析器,响应的时候会自动使用我们的视图解析器,自动加上后缀
解决方法:
applicationContext-mvc.xml注解目录扫描的时候加上 cn.afterturn.easypoi.view
<!-- 扫描easypoi中所有的view -->
<context:component-scan base-package="cn.afterturn.easypoi.view" />
<!-- Bean解析器,级别高于默认解析器,寻找bean对象进行二次处理 -->
<bean id="beanNameViewResolver"
class="org.springframework.web.servlet.view.BeanNameViewResolver" p:order="0">
</bean>
- 配置完成如果还是报错,点击导出的时候每次报错classNotfound ExportParams
但是普通测试的时候,并没有报错,所以可能只是在tomcat服务器运行时的错误
- 正常情况tomcat会将jar包自动放到项目下,但是有时候不会,右边会出现很多没有放过去的jar包,这时候服务器运行就会始终找不到,点击放入
- 完成导出功能
完整代码
得到图片的真实路径
//到处excel表格 /employee/download
@RequestMapping("/download")
public String download(EmployeeQuery query,ModelMap map, HttpServletRequest request) {
System.out.println("=================================adasd");
List<Employee> list = iEmployeeService.findByQuery(query);
//修改当前员工的头像路径 -> 真实路径
String realPath = request.getServletContext().getRealPath("");
list.forEach(e->{
e.setHeadImage(realPath + e.getHeadImage());
});
ExportParams params = new ExportParams("员工数据", "测试", ExcelType.XSSF);
//params.setFreezeCol(2); 冻结相应的列
map.put(NormalExcelConstants.DATA_LIST, list); // 数据集合
map.put(NormalExcelConstants.CLASS, Employee.class);//导出实体
map.put(NormalExcelConstants.PARAMS, params);//参数
map.put(NormalExcelConstants.FILE_NAME, "employee");//文件名称
//return "easypoiExcelView";
return NormalExcelConstants.EASYPOI_EXCEL_VIEW;//View名称
}
四、导入
- 准备导入页面
- 注意要上传文件时必须加上
method=“post” enctype=“multipart/form-data”
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%@include file="/WEB-INF/views/head.jsp" %>
</head>
<body>
<span style="color: red">${count}</span>
<!-- 上传请配置enctype -->
<form action="/import/xlsx" method="post" enctype="multipart/form-data">
<input class="easyui-filebox" name="xlsxFile" data-options="prompt:'选择一个文件...'" style="width:80%">
<button class="easyui-linkbutton" type="submit">确定</button>
</form>
</body>
</html>
ImportController层的准备
@Controller
@RequestMapping("/import")
public class ImportController {
@RequestMapping("/index")
public String index(){
System.out.println("进入导入功能");
return "import";
}
@RequestMapping("/xlsx")
public String importEmployee(MultipartFile xlsxFile,HttpServletResponse response) throws Exception {
return "import";
}
五、没有验证的导入
从外部将数据导入数据库
controller层代码
@RequestMapping("/xlsx")
public String importEmployee(MultipartFile xlsxFile) throws Exception {
InputStream inputStream = xlsxFile.getInputStream();
ImportParams params = new ImportParams();
//没有注释之前查出来是null
params.setTitleRows(1);
List<Employee> list = ExcelImportUtil.importExcel(
inputStream,
Employee.class, params);
list.forEach(u -> u);
return "import";
}
- xlsxFile.getInputStream();得到导入的文件流
- ImportParams params = new ImportParams();创建导入的参数设置对象
可以设置导入时的一些参数,和判断等等 - 不开启验证得到的是所有数据
ExcelImportUtil.importExcel
注意导入的excel字段名称要和实体类的注解excel的名称一致不然封装不了
开始测试,然后开启疯狂报错模式
@RequestMapping("/xlsx")
public String importEmployee(MultipartFile xlsxFile) throws Exception {
InputStream inputStream = xlsxFile.getInputStream();
ImportParams params = new ImportParams();
//没有注释之前查出来是null
params.setTitleRows(1);
List<Employee> list = ExcelImportUtil.importExcel(
inputStream,
Employee.class, params);
list.forEach(u -> iEmployeeService.save(u));
return "import";
}
因为我们在保存时将密码加密了,如果导入的用户密码为空,就会报错
所以在保存时设置初始密码123
继续测试又报错
个人理解:
我们前台直接添加时,持久层保存都是save方法,没有id可以自动创建,
保存employee时也没有传id为什么就不会报错?因为在封装employee之前jpa会自动去查询到部门,封装到员工对象,然后再保存员工
但是
现在使用的是easypoi,能自动识别吗?
不能,只通过一个部门的名称,不能封装成对象存入employee对象,id都为null,现在部门就只存了一个部门名字
employee对象中,就只有一个id为空的部门对象,只根据这个部门名称并不能确定部门对象
我们表中有什么数据,对应得上实体类的字段,就可以存入
Easypoi只能封装对象的字段,和对应的另一个实体类对象字段,而且是打了注解的才可以
- 解决方法
1.添加初始密码
2.将部门根据名称查出
思路整理
- 获取前台文件流MultipartFile
- 创建导入参数设置对象ImportParams
- 得到文件输入流inputStream
- ImportParams 设置参数
- ExcelImportUtil.importExce得到导入数据
- 更改初始密码
- 存入部门
@RequestMapping("/xlsx")
public String importEmployee(MultipartFile xlsxFile) throws Exception {
InputStream inputStream = xlsxFile.getInputStream();
ImportParams params = new ImportParams();
//没有注释之前查出来是null
params.setTitleRows(1);;
List<Employee> list = ExcelImportUtil.importExcel(
inputStream,
Employee.class, params);
for (Employee e : list) {
e.setPassword("123");
//通过员工的
if (e.getDepartment()!=null){
//Department{name='IT部', id=null} 现在部门的id是空,前台导入的时候不能确定部门,报错
//通过名字将部门查出来,再存入到这个员工对象中
System.out.println(iDepartmentService);
Department department = iDepartmentService.findByName(e.getDepartment().getName());
e.setDepartment(department);
}
iEmployeeService.save(e);
}
return "import";
}
基本的导入功能完成,但是用户名重复,年龄等等不符合标准
所以需要验证
六、加入验证功能
实体类注解
@Entity
@Table(name="employee")
public class Employee extends BaseDomain {
@Excel(name = "用户名")
@NotBlank(message = "用户名不能为空")
private String username;
private String password;
@Excel(name = "邮件",width = 25)
private String email;
@Max(value =60,message = "年纪最大为60岁")
@Excel(name = "年纪")
private Integer age;
@Excel(name = "头像",type = 2,width = 10 , height = 20)
//加入头像字段
private String headImage;
}
Controller层启用验证
//设置启用 验证
params.setNeedVerfiy(true);
获得导入的数据
//获取到所有导入的数据
ExcelImportResult<Employee> result = ExcelImportUtil.importExcelMore(inputStream, Employee.class, params);
注意现在是使用 ExcelImportUtil.importExcelMore获取导入 数据
现在控制层就只将通过验证的数据保存
@RequestMapping("/xlsx")
public String importEmployee(MultipartFile xlsxFile,HttpServletResponse response) throws Exception {
InputStream inputStream = xlsxFile.getInputStream();
/* 获取导入参数*/
ImportParams params = new ImportParams();
//没有注释之前查出来是null
params.setTitleRows(1);
//设置启用 验证
params.setNeedVerfiy(true);
//获取到所有导入的数据
ExcelImportResult<Employee> result = ExcelImportUtil.importExcelMore(inputStream, Employee.class, params);
/*得到成功的数据*/
for (Employee e : result.getList()) {
System.out.println(e+"成功的数据===============================");
e.setPassword("123");
//通过员工的
if (e.getDepartment()!=null){
//Department{name='IT部', id=null} 现在部门的id是空,前台导入的时候不能确定部门,报错
//通过名字将部门查出来,再存入到这个员工对象中
System.out.println(iDepartmentService);
Department department = iDepartmentService.findByName(e.getDepartment().getName());
e.setDepartment(department);
}
iEmployeeService.save(e);
}
return "import";
}
- 基本验证完成,但是用户名重复还是不能解决
- 导入失败的数据应该单独存起来
七、自定义验证器
1.实现接口
2.确定泛型
3.开启扫描,告诉springMvc这个类
4.通过查询数据库判断是否重复用户名
5.@Component注解 组件 交给spring管理
@Component
public class MyExcelVerifyHandler implements IExcelVerifyHandler<Employee> {
//确定泛型
@Autowired
private IEmployeeService iEmployeeService;
@Override
public ExcelVerifyHandlerResult verifyHandler(Employee employee) {
ExcelVerifyHandlerResult result = new ExcelVerifyHandlerResult();
//判断是否重名,如果用户名能用,返回true
Boolean bool = iEmployeeService.checkName(employee.getUsername());
if (!bool){
result.setMsg("用户名重复了");
result.setSuccess(false);
return result;
}
/*没有重复就返回true,通过验证*/
result.setSuccess(true);
return result;
}
}
错误文件
easyPOI已经自动将错误文件生成了,直接使用
文件下载功能,直接返回浏览器,下载错误文件
if (result.isVerfiyFail()){
//错误的文件
//easyPOI自动已经生成了错误文件
Workbook wb = result.getFailWorkbook();
System.out.println(wb);
//选择文件的类型,从tomcat的conf打开web.xml选择对应的文件类型
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); //mime类型
response.setHeader("Content-disposition", "attachment;filename=error.xlsx");
//设置不要缓存
response.setHeader("Pragma", "No-cache");
OutputStream ouputStream = response.getOutputStream();
wb.write(ouputStream);
ouputStream.flush();
ouputStream.close();
}
错误提示也保存了
完整controller代码
@RequestMapping("/xlsx")
public String importEmployee(MultipartFile xlsxFile,HttpServletResponse response) throws Exception {
InputStream inputStream = xlsxFile.getInputStream();
/* 获取导入参数*/
ImportParams params = new ImportParams();
//没有注释之前查出来是null
params.setTitleRows(1);
//设置启用 验证
params.setNeedVerfiy(true);
//放入自定义验证
params.setVerifyHandler(myExcelVerifyHandler);
//获取到所有导入的数据
ExcelImportResult<Employee> result = ExcelImportUtil.importExcelMore(inputStream, Employee.class, params);
/*得到成功的数据*/
for (Employee e : result.getList()) {
System.out.println(e+"成功的数据===============================");
e.setPassword("123");
//通过员工的
if (e.getDepartment()!=null){
//Department{name='IT部', id=null} 现在部门的id是空,前台导入的时候不能确定部门,报错
//通过名字将部门查出来,再存入到这个员工对象中
System.out.println(iDepartmentService);
Department department = iDepartmentService.findByName(e.getDepartment().getName());
e.setDepartment(department);
}
iEmployeeService.save(e);
}
if (result.isVerfiyFail()){
//错误的文件
//easyPOI自动已经生成了错误文件
Workbook wb = result.getFailWorkbook();
System.out.println(wb);
//选择文件的类型,从tomcat的conf打开web.xml选择对应的文件类型
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); //mime类型
response.setHeader("Content-disposition", "attachment;filename=error.xlsx");
//设置不要缓存
response.setHeader("Pragma", "No-cache");
OutputStream ouputStream = response.getOutputStream();
wb.write(ouputStream);
ouputStream.flush();
ouputStream.close();
}
return "import";
}