官方文档:http://doc.wupaas.com/docs/easypoi/
Easypoi功能如同名字easy,主打的功能就是容易,让一个没见接触过poi的人员就可以方便的写出Excel导出,Excel模板导出,Excel导入,Word模板导出,通过简单的注解和模板语言(熟悉的表达式语法),完成以前复杂的写法。
如果使用maven,请使用如下坐标:
<dependency>
<groupId>cn.afterturngroupId>
<artifactId>easypoi-baseartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>cn.afterturngroupId>
<artifactId>easypoi-webartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>cn.afterturngroupId>
<artifactId>easypoi-annotationartifactId>
<version>4.1.0version>
dependency>
easypoi起因就是Excel的导入导出,最初的模板是实体和Excel的对应,model–row,filed–col 这样利用注解我们可以和容易做到excel到导入导出
经过一段时间发展,现在注解有5个类分别是
作用于实体类上,同时实体类需要实现序列化接口。value需要给定一个唯一的ID,使用类名即可。
@ExcelTarget("user")
public class User implements Serializable {
作用于实体类的属性上,每一个属性将会对应Excel上的一列。
属性 | 类型 | 默认值 | 功能 |
---|---|---|---|
name | String | null | 列名,支持name_id |
needMerge | boolean | fasle | 是否需要纵向合并单元格(用于含有list中,单个的单元格,合并list创建的多个row) |
orderNum | String | “0” | 列的排序,支持name_id |
replace | String[] | {} | 值得替换 导出是{a_id,b_id} 导入反过来 |
savePath | String | “upload” | 导入文件保存路径,如果是图片可以填写,默认是upload/className/ IconEntity这个类对应的就是upload/Icon/ |
type | int | 1 | 导出类型 1 是文本 2 是图片,3 是函数,10 是数字 默认是文本 |
width | double | 10 | 列宽 |
isStatistics | boolean | fasle | 自动统计数据,在追加一行统计,把所有数据都和输出[这个处理会吞没异常,请注意这一点] |
isHyperlink | boolean | false | 超链接,如果是需要实现接口返回对象 |
isImportField | boolean | true | 校验字段,看看这个字段是不是导入的Excel中有,如果没有说明是错误的Excel,读取失败,支持name_id |
exportFormat | String | “” | 导出的时间格式,以这个是否为空来判断是否需要格式化日期 |
importFormat | String | “” | 导入的时间格式,以这个是否为空来判断是否需要格式化日期 |
format | String | “” | 时间格式,相当于同时设置了exportFormat 和 importFormat |
databaseFormat | String | “yyyyMMddHHmmss” | 导出时间设置,如果字段是Date类型则不需要设置 数据库如果是string 类型,这个需要设置这个数据库格式,用以转换时间格式输出 |
numFormat | String | “” | 数字格式化,参数是Pattern,使用的对象是DecimalFormat |
imageType | int | 1 | 导出类型 1 从file读取 2 是从数据库中读取 默认是文件 同样导入也是一样的 |
suffix | String | “” | 文字后缀,如% 90 变成90% |
isWrap | boolean | true | 是否换行 即支持\n |
mergeRely | int[] | {} | 合并单元格依赖关系,比如第二列合并是基于第一列 则{0}就可以了 |
mergeVertical | boolean | fasle | 纵向合并内容相同的单元格 |
fixedIndex | int | -1 | 对应excel的列,忽略名字 |
isColumnHidden | boolean | false | 导出隐藏列 |
一对多的集合注解,用以标记集合是否被数据以及集合的整体排序。
属性 | 类型 | 默认值 | 功能 |
---|---|---|---|
id | String | null | 定义ID |
name | String | null | 定义集合列名,支持nanm_id |
orderNum | int | 0 | 排序,支持name_id |
type | Class> | ArrayList.class | 导入时创建对象使用 |
@ExcelCollection(name = "订单列表",orderNum = "8")
private List<Order> orders;
@ExcelTarget("orders")
@Data
@AllArgsConstructor
public class Order implements Serializable {
/**
* 订单编号
*/
@Excel(name = "订单编号",orderNum = "8",width = 20.0)
private String num;
/**
* 订单名称
*/
@Excel(name = "订单名称",orderNum = "9",width = 20.0)
private String name;
}
一对一实体对应,作用在实体类里面的属性又是一个实体类的情况。同时被组合的实体类也需要使用ExcelTarget和Excel进行标识。同时实现序列化接口。
@ExcelEntity
private Department department;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ExcelTarget("department")
public class Department implements Serializable {
/**
* 部门ID
*/
private Integer deptId;
/**
* 部门名称
*/
@Excel(name = "部门")
private String deptName;
/**
* 地址
*/
private String address;
}
忽略这个属性。
<dependencies>
<dependency>
<groupId>cn.afterturngroupId>
<artifactId>easypoi-baseartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>cn.afterturngroupId>
<artifactId>easypoi-webartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>cn.afterturngroupId>
<artifactId>easypoi-annotationartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.16version>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiterartifactId>
<version>RELEASEversion>
<scope>compilescope>
dependency>
dependencies>
/**
* @ClassName: User
* @Description: 用户实体类,需要实现对象序列化接口
* @author: 莫提
* @date 2020/11/27 8:55
* @Version: 1.0
*/
@Data
@AllArgsConstructor
@Builder
@ExcelTarget("user")
public class User implements Serializable {
/**
* 用户 ID
*/
@Excel(name = "编号",orderNum = "0",suffix = "号")
private Integer id;
/**
* 姓名
*/
@Excel(name = "姓名",orderNum = "1")
private String name;
/**
* 性别
*/
@Excel(name = "性别",orderNum = "2",replace = {"男性_男","女性_女"})
private String sex;
/**
* 生日
*/
@Excel(name = "生日",width = 35.0,format = "yyyy-MM-dd HH:mm:ss",orderNum = "4")
private Date birthday;
/**
* 用户状态:【1:正常】【0:封禁】
*/
@Excel(name = "用户状态",replace = {"正常_1","封禁_0"},orderNum = "3")
private Integer status;
/**
* 密码
*/
@ExcelIgnore
private String password;
/**
* 爱好
*/
@Excel(name = "爱好",orderNum = "5",width = 40.0)
private List<String> hobbies;
/**
* 爱好字符串
*/
@Excel(name = "爱好",orderNum = "5",width = 40.0)
private String hobbyStr;
/**
* 身份证【一对一】
*/
@ExcelEntity
private Card card;
/**
* 订单列表【一对多】
*/
@ExcelCollection(name = "订单列表",orderNum = "8")
private List<Order> orders;
/**
* 头像信息,type = 2表示图片
*/
@Excel(name = "头像",width = 20,height = 40,type = 2)
private String photo;
public String getHobbyStr(){
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hobbies.size(); i++) {
sb.append(hobbies.get(i));
if (i != hobbies.size() -1){
sb.append(",");
}
}
return sb.toString();
}
}
/**
* @ClassName: Card
* @Description: 身份证
* @author: 莫提
* @date 2020/11/27 8:55
* @Version: 1.0
*/
@ExcelTarget("card")
@Data
@AllArgsConstructor
public class Card implements Serializable {
/**
* 身份证号
*/
@Excel(name = "身份证号码",width = 20.0,orderNum = "6")
private String number;
/**
* 地址
*/
@Excel(name = "住址",width = 50.0,orderNum = "7")
private String address;
}
/**
* @ClassName: Order
* @Description: 订单
* @author: 莫提
* @date 2020/11/27 8:55
* @Version: 1.0
*/
@ExcelTarget("orders")
@Data
@AllArgsConstructor
public class Order implements Serializable {
/**
* 订单编号
*/
@Excel(name = "订单编号",orderNum = "8",width = 20.0)
private String num;
/**
* 订单名称
*/
@Excel(name = "订单名称",orderNum = "9",width = 20.0)
private String name;
}
/**
* @ClassName: Employee
* @Description: 导入的员工实体
* @author: 莫提
* @date 2020/11/27 8:55
* @Version: 1.0
*/
@ExcelTarget("employee")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
/**
* 用户 ID
*/
@Excel(name = "编号",suffix = "号")
private Integer id;
/**
* 姓名
*/
@Excel(name = "姓名")
private String name;
/**
* 性别
*/
@Excel(name = "性别")
private String sex;
/**
* 生日
*/
@Excel(name = "生日",format = "yyyy-MM-dd HH:mm:ss")
private Date birthday;
/**
* 用户状态:【1:正常】【0:封禁】
*/
@Excel(name = "用户状态",replace = {"正常_1","封禁_0"})
private Integer status;
}
public class TestExport {
/**
* 获取全部用户
*/
public List<User> getAllUsers(){
List<User> list = new ArrayList<>();
// 创建身份证信息
Card card1 = new Card("11111","江西省南昌市");
Card card2 = new Card("22222","四川省成都市");
Card card3 = new Card("33333","河北省唐山市");
// 创建订单信息
Order order1 = new Order("1","泡面");
Order order2 = new Order("2","外套");
Order order3 = new Order("3","裤子");
Order order4 = new Order("4","AJ1");
Order order5 = new Order("5","NICK");
Order order6 = new Order("6","笔记本");
Order order7 = new Order("7","键盘");
Order order8 = new Order("8","鼠标");
list.add(User.builder()
.id(1).name("张三").sex("男").status(0).password("123456").birthday(new Date())
.hobbies(Arrays.asList("唱歌", "跳舞")).card(card1).orders(Arrays.asList(order1,order2))
.photo("E:\\头像\\avatar.jpg")
.build());
list.add(User.builder()
.id(2).name("李四").sex("男").status(1).password("123456").birthday(new Date())
.hobbies(Arrays.asList("睡觉", "游戏")).card(card2).orders(Arrays.asList(order3,order4,order5))
.photo("E:\\头像\\1.jpg")
.build());
list.add(User.builder()
.id(3).name("丽丽").sex("女").status(0).password("123456").birthday(new Date())
.hobbies(Arrays.asList("技术", "代码")).card(card3).orders(Arrays.asList(order6,order7,order8))
.photo("E:\\头像\\2.jpg")
.build());
return list;
}
/**
* 导出Excel表格
*/
@Test
public void testExport() throws IOException {
// 获取全部用户
List<User> users = getAllUsers();
// 导出到文件
FileOutputStream outputStream = new FileOutputStream("E:\\导出.xls");
// 配置Excel文件信息
ExportParams params = new ExportParams();
params.setTitle("用户信息");
params.setSheetName("用户信息1表");
// 导出Excel表格
Workbook workbook = ExcelExportUtil.exportExcel(params, User.class, users);
workbook.write(outputStream);
// 关闭资源
outputStream.close();
workbook.close();
}
}
public class TestImport {
/**
* 导入
*/
@Test
public void testImport() throws Exception {
// 参数1:文件流
FileInputStream stream = new FileInputStream("E:\\导入.xls");
// 参数2:导入类型
ImportParams params = new ImportParams();
// 标题占用多少行
params.setTitleRows(1);
// 头部属性占用多少行
params.setHeadRows(1);
// 从指定的sheet的下标开始读取
// params.setStartSheetIndex(1);
// 读取sheet的数量,需要和上面的配合
// params.setSheetNum(1);
// 对Excle进行合法参数校验
params.setImportFields(new String[]{"编号"});
// 参数3:导出的数据结合
List<Employee> employees = ExcelImportUtil.importExcel(stream, Employee.class, params);
for (Employee employee : employees) {
System.out.println("employee = " + employee);
}
}
}
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.20version>
dependency>
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.0.3version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.1.10version>
dependency>
<dependency>
<groupId>cn.afterturngroupId>
<artifactId>easypoi-baseartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>cn.afterturngroupId>
<artifactId>easypoi-webartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>cn.afterturngroupId>
<artifactId>easypoi-annotationartifactId>
<version>4.1.0version>
dependency>
dependencies>
# 配置应用相关
server:
servlet:
context-path: /
session:
timeout: 60m
tomcat:
uri-encoding: UTF-8
# 配置MyBatis相关
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
spring:
# 禁用ThymeLeaf缓存
thymeleaf:
cache: false
# 配置数据源
datasource:
username: root
password: 983934
url: jdbc:mysql://127.0.0.1:3306/learn?serverTimezone=Hongkong&useAffectedRows=true
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
`work_age` int(2) DEFAULT NULL COMMENT '工龄',
`sex` int(1) DEFAULT NULL COMMENT '性别',
`birthday` date DEFAULT NULL COMMENT '生日',
`dept_id` int(11) DEFAULT NULL COMMENT '部门ID',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=18433 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `department`
-- ----------------------------
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
`dept_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '部门ID',
`dept_name` varchar(20) DEFAULT NULL,
`address` varchar(100) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`dept_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
生成基本的增删查改方法
/**
* (User)实体类
*
* @author 莫提
* @since 2020-12-13 20:39:36
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ExcelTarget("user")
public class User implements Serializable {
private static final long serialVersionUID = 587947964930607265L;
/**
* 用户ID
*/
@Excel(name = "ID",suffix = "号")
private Integer userId;
/**
* 用户名
*/
@Excel(name = "姓名")
private String userName;
/**
* 工龄
*/
@Excel(name = "工龄",suffix = "年")
private Integer workAge;
/**
* 性别
*/
@Excel(name = "性别",replace = {"男_1","女_0"})
private Integer sex;
/**
* 生日
*/
@Excel(name = "生日",format = "yyyy年MM月dd日",width = 20)
private Date birthday;
/**
* 部门ID
*/
@ExcelIgnore
private Integer deptId;
/**
* 所属部门
*/
@ExcelEntity
private Department department;
}
package com.moti.entity;
import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelTarget;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* (Department)实体类
*
* @author 莫提
* @since 2020-12-13 20:39:38
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ExcelTarget("department")
public class Department implements Serializable {
private static final long serialVersionUID = 164895400081727426L;
/**
* 部门ID
*/
private Integer deptId;
/**
* 部门名称
*/
@Excel(name = "部门")
private String deptName;
/**
* 地址
*/
private String address;
}
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>主页title>
<link rel="stylesheet" href="boot/css/bootstrap.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="js/jquery-3.4.1.js">script>
head>
<body>
<hr>
<div class="container">
<div class="row">
<div class="col-12">
<h3>选择Excle文件导入h3>
<form class="form-inline" method="post" action="importExcel" enctype="multipart/form-data">
<input type="file" class="form-control" name="file">
<input type="submit" class="btn btn-danger" value="导入">
form>
div>
<br>
<div class="col-12">
<table class="table table-bordered">
<thead>
<tr>
<th>IDth>
<th>姓名th>
<th>部门th>
<th>性别th>
<th>工龄th>
<th>出生日期th>
tr>
thead>
<tbody>
<tr th:each="user:${users}">
<td th:text="${user.userId}">td>
<td th:text="${user.userName}">td>
<td th:text="${user.department.deptName}">td>
<td th:if="${user.sex == 1}">男td>
<td th:if="${user.sex == 0}">女td>
<td th:text="${user.workAge}">td>
<td th:text="${#dates.format(user.birthday,'yyyy-MM-dd')}">td>
tr>
tbody>
table>
<br>
<a href="exportExcel" class="btn btn-success btn-sm">导出a>
div>
div>
div>
body>
html>
/**
* @ClassName: HelloController
* @Description:
* @author: 莫提
* @date 2020/11/27 8:55
* @Version: 1.0
*/
@Controller
@Slf4j
public class HelloController {
@Autowired
private UserService userService;
@Autowired
private DepartmentService departmentService;
@GetMapping("/")
public String hello(Map<String,Object> map){
// 获取全部用户
List<User> users = userService.listUsers();
users.forEach(user -> {
Department department = departmentService.getById(user.getDeptId());
user.setDepartment(department);
});
map.put("users",users);
return "index";
}
/**
* 导入
*/
@PostMapping("/importExcel")
public String importExcel(MultipartFile file) throws Exception {
if (ObjectUtils.isEmpty(file) || file.getSize() == 0){
return "redirect:/";
}
log.info("接收到文件:{}",file.getOriginalFilename());
// 参数1:文件流
InputStream stream = file.getInputStream();
// 参数2:导入类型
ImportParams params = new ImportParams();
// 标题占用多少行
params.setTitleRows(1);
// 头部属性占用多少行
params.setHeadRows(1);
// 从指定的sheet的下标开始读取
// params.setStartSheetIndex(1);
// 读取sheet的数量,需要和上面的配合
// params.setSheetNum(1);
// 对Excle进行合法参数校验
params.setImportFields(new String[]{"姓名","部门"});
List<User> users = ExcelImportUtil.importExcel(stream, User.class,params);
// 遍历结果,插入到数据库
users.forEach(user -> {
Department build = Department.builder().deptName(user.getDepartment().getDeptName()).build();
List<Department> departments = departmentService.listDepartments(build);
if (departments.size() > 0){
user.setDeptId(departments.get(0).getDeptId());
}
userService.insert(user);
});
log.info("导入用户:{}",users);
return "redirect:/";
}
/**
* 导出Excel
*/
@GetMapping("/exportExcel")
public void exportExcel(HttpServletResponse response) throws IOException {
response.setHeader("content-disposition",
"attachment;fileName="+ URLEncoder.encode("用户列表.xls","UTF-8"));
ServletOutputStream outputStream = response.getOutputStream();
// 查询所有用户
List<User> users = userService.listUsers();
users.forEach(user -> {
Department department = departmentService.getById(user.getDeptId());
user.setDepartment(department);
});
// 生成文件的信息
ExportParams params = new ExportParams();
params.setTitle("导出的用户信息");
params.setSheetName("用户信息");
Workbook workbook = ExcelExportUtil.exportExcel(params, User.class, users);
// 输出
workbook.write(outputStream);
// 关闭资源
outputStream.close();
workbook.close();
}
}
导入之后