学习EasyPOI
1. 简介
官方网站:http://doc.wupaas.com/docs/easypoi/easypoi-1c0u6ksp2r091。
Easypoi
的目标不是替代 poi
,而是让一个不懂导入导出的快速使用 poi
完成 Excel
和 word
的各种操作,而不是看很多 api
才可以完成这样工作。
2. 使用EasyPOI
2.1 环境搭建
引入相关依赖
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>4.4.0</version>
</dependency>
2.2 相关注解
**
> easypoi起因就是Excel的导入导出,最初的模板是实体和Excel的对应,model--row,filed--col 这样利用注解我们可以和容易做到excel到导入导出
**
举例:
java中有一个User类,属性有id、name、age等等;
那么
- User类代表Excel文件
- 一个User对象代表Excel文件中的一行
- User类的每个属性(id、name、age)代表Excel中的一列
@Excel
作用到filed
上面,是对Excel
一列的一个描述。用于类里面的属性。@ExcelCollection
表示一个集合,主要针对一对多的导出,比如一个老师对应多个科目,科目就可以用集合表示。类里面的属性是一个集合。@ExcelEntity
表示一个继续深入导出的实体,但他没有太多的实际意义,只是告诉系统这个对象里面同样有导出的字段。@ExcelIgnore
和名字一样表示这个字段被忽略跳过这个导出。使用该注解的属性不会导出到Excel中。@ExcelTarget
这个是作用于最外层的对象,描述这个对象的id,以便支持一个对象可以针对不同导出做出不同处理。用于对应Excel的那个类,需要定义一个唯一的标识
@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 | 列宽 |
height | double | 10 | 列高,后期打算统一使用@ExcelTarget的height,这个会被废弃,注意 |
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 | 导出隐藏列 |
@ExcelTarget
属性 | 类型 | 默认值 | 功能 |
---|---|---|---|
value | String | null | 定义ID |
height | double | 10 | 设置行高 |
fontSize | short | 11 | 设置文字大小 |
@ExcelEntity
属性 | 类型 | 默认值 | 功能 |
---|---|---|---|
id | String | null | 定义ID |
@ExcelCollection
属性 | 类型 | 默认值 | 功能 |
---|---|---|---|
id | String | null | 定义ID |
name | String | null | 定义集合列名,支持nanm_id |
orderNum | int | 0 | 排序,支持name_id |
type | Class<?> | ArrayList.class | 导入时创建对象使用 |
@ExcelIngore
忽略这个属性,多使用循环引用中。
3. 导出Excel
注意:导出Excel的对象必须实现序列化接口
3.1 导出基本数据
-
定义对象
-
@Data @ExcelTarget("users") public class User implements Serializable { @Excel(name = "编号", orderNum = "1") private String id; @Excel(name = "姓名", orderNum = "2") private String name; @Excel(name = "年龄", orderNum = "4", suffix = "$", replace = {"10岁_10", "12岁_12"}) //10岁替代10 private Integer age; @Excel(name = "生日", width = 30.0, format = "yyyy-MM-dd HH:mm:ss", orderNum = "3") private Date birthday; }
-
-
测试数据
-
public class TestPOI { public List<User> getUser() { List<User> users = new ArrayList<>(); for (int i = 0; i < 10; i++) { User user = new User(); user.setId(String.valueOf(i)); user.setName("小杰" + i); user.setAge(10 + i); user.setBirthday(new Date()); users.add(user); } return users; } // 导出 @Test public void testExport() throws IOException { List<User> users = getUser(); // 导出Excel // ExcelExportUtil.exportExcel三个参数: // 参数1:ExportParams对象 参数2:导出的类型,即Excel对应的类 参数3:导出的数据 Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("用户信息列表", "用户信息"), User.class, users); // 将Excel写出指定位置 FileOutputStream outputStream = new FileOutputStream("D:/写代码/Java/LearnJava/LearnEasyPOI/src/test/java/test.xlsx"); workbook.write(outputStream); outputStream.close(); workbook.close(); } }
-
3.2 导出list集合
3.2.1 默认格式
@Data
@ExcelTarget("users")
public class User implements Serializable {
//...
@Excel(name = "爱好", width = 30.0, orderNum = "5")
private List<String> hobbies;
}
默认格式如下:
3.2.2 自定义导出格式
自定义导出格式可以在对应的 set 方法中进行处理
@Data
@ExcelTarget("users")
public class User implements Serializable {
// ......
// @Excel(name = "爱好", width = 30.0, orderNum = "5")
@ExcelIgnore
private List<String> hobbies;
@Excel(name = "爱好", width = 30.0, orderNum = "5")
private String hobby;
public String getHobby() {
StringBuilder builder = new StringBuilder();
hobbies.forEach(e -> {
builder.append(e).append("、");
});
return builder.substring(0, builder.length() - 1).toString();
}
}
3.3 对象中含有对象
@Data
@ExcelTarget("cards")
public class Card implements Serializable {
@Excel(name = "身份证号码", width = 20.0, orderNum = "6")
private String number;
@Excel(name = "籍贯", width = 40.0, orderNum = "7")
private String address;
}
@Data
@ExcelTarget("users")
public class User implements Serializable {
// ......
@ExcelEntity // 标志对象之间
private Card card;
}
3.4 导出一对多关系
@Data
@NoArgsConstructor
@AllArgsConstructor
@ExcelTarget("orders")
public class Order implements Serializable {
@Excel(name = "订单编号", orderNum = "8", width = 20.0)
private String no;
@Excel(name = "订单名称", orderNum = "9", width = 15.0)
private String name;
}
@Data
@ExcelTarget("users")
public class User implements Serializable {
// ......
@ExcelCollection(name = "订单列表", orderNum = "8") // 一对多
private List<Order> orders;
}
3.5 导出图片
@Data
@ExcelTarget("users")
public class User implements Serializable {
// ......
@Excel(name = "头像", type = 2, width = 20)// 2表示图像
private String photo;
}
3.6 大数据量导出
大数据导出是当我们的导出数量在几万到上百万的数据时,一次从数据库查询这么多数据加载到内存然后写入会对我们的内存和CPU都产生压力,这个时候需要我们像分页一样处理导出分段写入 Excel
缓解 Excel
的压力。
@Test
public void bigDataExport() throws Exception {
List<MsgClient> list = new ArrayList<MsgClient>();
Workbook workbook = null;
Date start = new Date();
ExportParams params = new ExportParams("大数据测试", "测试");
for (int i = 0; i < 1000000; i++) { //一百万数据量
MsgClient client = new MsgClient();
client.setBirthday(new Date());
client.setClientName("小明" + i);
client.setClientPhone("18797" + i);
client.setCreateBy("JueYue");
client.setId("1" + i);
client.setRemark("测试" + i);
MsgClientGroup group = new MsgClientGroup();
group.setGroupName("测试" + i);
client.setGroup(group);
list.add(client);
if(list.size() == 10000){
workbook = ExcelExportUtil.exportBigExcel(params, MsgClient.class, list);
list.clear();
}
}
ExcelExportUtil.closeExportBigExcel();
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/ExcelExportBigData.bigDataExport.xlsx");
workbook.write(fos);
fos.close();
}
4. 导入Excel
4.1 基本使用
-
编写对应的实体类
-
@Data @ExcelTarget("Employee") public class Employee implements Serializable { @Excel(name = "编号") private String id; @Excel(name = "姓名") private String name; @Excel(name = "生日", format = "yyyy-MM-dd HH:mm:ss") private Date birthday; @Excel(name = "年龄") private Integer age; }
-
-
测试
-
@Test public void testImport() throws Exception { ImportParams importParams = new ImportParams(); importParams.setTitleRows(1); // 标题列占几行 importParams.setHeadRows(2); //header占几行 // 参数1:带入的excel文件流;参数2:导入类型;参数3:导入的配置对象 List<Employee> objects = ExcelImportUtil.importExcel(new FileInputStream("D:/写代码/Java/LearnJava/LearnEasyPOI/src/test/java/test.xlsx"), Employee.class, importParams); objects.forEach(System.out::println); }
-
Employee(id=0, name=小杰0, birthday=Wed Mar 30 19:42:54 CST 2022, age=10)
Employee(id=1, name=小杰1, birthday=Wed Mar 30 19:42:54 CST 2022, age=11)
Employee(id=2, name=小杰2, birthday=Wed Mar 30 19:42:54 CST 2022, age=12)
Employee(id=3, name=小杰3, birthday=Wed Mar 30 19:42:54 CST 2022, age=13)
Employee(id=4, name=小杰4, birthday=Wed Mar 30 19:42:54 CST 2022, age=14)
Employee(id=5, name=小杰5, birthday=Wed Mar 30 19:42:54 CST 2022, age=152)
Employee(id=6, name=小杰6, birthday=Wed Mar 30 19:42:54 CST 2022, age=16)
Employee(id=7, name=小杰7, birthday=Wed Mar 30 19:42:54 CST 2022, age=17)
Employee(id=8, name=小杰8, birthday=Wed Mar 30 19:42:54 CST 2022, age=18)
Employee(id=9, name=小杰9, birthday=Wed Mar 30 19:42:54 CST 2022, age=19)
4.2 相关参数
属性 | 类型 | 默认值 | 功能 |
---|---|---|---|
titleRows | int | 0 | 表格标题行数,默认0 |
headRows | int | 1 | 表头行数,默认1 |
startRows | int | 0 | 字段真正值和列标题之间的距离 默认0 |
keyIndex | int | 0 | 主键设置,如何这个cell没有值,就跳过 或者认为这个是list的下面的值 这一列必须有值,不然认为这列为无效数据 |
startSheetIndex | int | 0 | 开始读取的sheet位置,默认为0 |
sheetNum | int | 1 | 上传表格需要读取的sheet 数量,默认为1 |
needSave | boolean | false | 是否需要保存上传的Excel |
needVerfiy | boolean | false | 是否需要校验上传的Excel |
saveUrl | String | “upload/excelUpload” | 保存上传的Excel目录,默认是 如 TestEntity这个类保存路径就是 upload/excelUpload/Test/yyyyMMddHHmss** 保存名称上传时间*五位随机数 |
verifyHanlder | IExcelVerifyHandler | null | 校验处理接口,自定义校验 |
lastOfInvalidRow | int | 0 | 最后的无效行数,不读的行数 |
readRows | int | 0 | 手动控制读取的行数 |
importFields | String[] | null | 导入时校验数据模板,是不是正确的Excel |
keyMark | String | ”:” | Key-Value 读取标记,以这个为Key,后面一个Cell 为Value,多个改为ArrayList |
readSingleCell | boolean | false | 按照Key-Value 规则读取全局扫描Excel,但是跳过List读取范围提升性能 仅仅支持titleRows + headRows + startRows 以及 lastOfInvalidRow |
dataHanlder | IExcelDataHandler | null | 数据处理接口,以此为主,replace,format都在这后面 |
4.3 Excel导入小功能
- 读取指定的sheet
- 比如要读取上传得第二个sheet 那么需要把startSheetIndex = 1 就可以了
- 读取几个sheet
- 比如读取前2个sheet,那么 sheetNum=2 就可以了
- 读取第二个到第五个sheet
- 设置 startSheetIndex = 1 然后sheetNum = 4
- 读取全部的sheet sheetNum
- 设置大点就可以了
- 保存Excel
- 设置 needVerfiy = true,默认保存的路径为upload/excelUpload/Test/yyyyMMddHHmss 保存名称上传时间 五位随机数 如果自定义路径 修改下saveUrl 就可以了,同时saveUrl也是图片上传时候的保存的路径
- 判断一个Excel是不是合法的Excel
- importFields 设置下值,就是表示表头必须至少包含的字段,如果缺一个就是不合法的excel,不导入
5. 集成web实现导入导出
5.1 环境搭建
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>4.4.0</version>
</dependency>
spring:
thymeleaf:
cache: false
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/easypoi?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 7012+2
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.lyj.easypoi_springboot.entity
5.2 导入功能
Controller
@Slf4j
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 导入Excel文件
@RequestMapping("/import")
public String importExcel(MultipartFile excelFile) throws Exception {
log.info("文件名称: [{}]", excelFile.getOriginalFilename());
// excel导入
ImportParams params = new ImportParams();
params.setTitleRows(1);
params.setHeadRows(1);
List<User> objects = ExcelImportUtil.importExcel(excelFile.getInputStream(), User.class, params);
objects.forEach(System.out::println);
userService.saveAll(objects);
return "redirect:/user/findAll";
}
// 查询所有
@RequestMapping("/findAll")
public String findAll(Model model){
List<User> users = userService.findAll();
model.addAttribute("users", users);
return "index";
}
}
Service
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
// 查询所有
@Override
public List<User> findAll() {
return userDAO.findAll();
}
// 导入Excel
@Override
public void saveAll(List<User> objects) {
objects.forEach(user -> {
user.setId(null);
userDAO.save(user);
});
}
}
Mapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.lyj.easypoi_springboot.dao.UserDAO">
<select id="findAll" resultType="com.lyj.easypoi_springboot.entity.User">
select
id,name,bir,no,address
from
t_user
</select>
<insert id="save" parameterType="com.lyj.easypoi_springboot.entity.User" useGeneratedKeys="true" keyProperty="id">
insert into t_user values (#{id},#{name},#{bir},#{no},#{address})
</insert>
</mapper>
5.3 导出功能
// 导出Excel文件
@RequestMapping("/export")
public void exportExcel(HttpServletResponse response) throws Exception {
// 查询所有数据
List<User> users = userService.findAll();
//导出excel
response.setHeader("content-disposition", "attachment;fileName=" + URLEncoder.encode("用户列表.xls", "UTF-8"));
ServletOutputStream outputStream = response.getOutputStream();
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("学生列表信息", "学生信息"), User.class, users);
workbook.write(outputStream);
outputStream.close();
workbook.close();
}