文章目录
- Spring Boot集成EasyPoi实现导入导出操作
- 0 简要说明
- 1 环境搭建
- 1.1 项目目录
- 1.2 依赖管理
- 2.3 关于swagger处理
- 2.4 关于切面处理耗时
- 1 自定义注解
- 2 定义切面类
- 3 如何使用
- 2.5 核心导入操作
- 2.6 核心导出操作
- 2 最佳实线
- 2.1 导入操作
- 1 实体类说明
- 2 业务层
- 3 效果
- 3 控制层
- 2.2 导出操作
- 2.3 大数据量处理
- 4 问题
- 4.1 日期类型字段特殊处理
- 4.2 文件上传
- 4.3 指定导出路径
参考博客
1.官网
Spring Boot集成EasyPoi实现导入导出操作
0 简要说明
Excel自适应xls和xlsx两种格式,word只支持docx模式
主要特点
1.设计精巧,使用简单
2.接口丰富,扩展简单
3.默认值多,write less do more
4.spring mvc支持,web导出可以简单明了
1 环境搭建
1.1 项目目录
1.2 依赖管理
<!--Spring 环境-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.9.1</version>
</dependency>
<!--MySQL 驱动包-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.31</version>
</dependency>
<!--自动配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</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>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--接口平台-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.7</version>
</dependency>
<!--commons-lang3工具包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!--Mybatis Plus 扩展包-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.5.1</version>
</dependency>
<!--Mybatis Plus 依赖包-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!--easypoi-->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
<!--easyexcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<!--接口平台-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
2.3 关于swagger处理
依赖
<!--接口平台-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.7</version>
</dependency>
<!--接口平台-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
配置类
package com.geekmice.springbootselfexercise.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
/**
* @BelongsProject: spring-boot-scaffold
* @BelongsPackage: com.geekmice.sbhelloworld.com.geekmice.sbpagehelper.config
* @Author: pingmingbo
* @CreateTime: 2023-07-30 15:45
* @Description: TODO
* @Version: 1.0
*/
@Configuration
public class Knife4jConfig {
@Bean(value = "defaultApi2")
public Docket customDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.geekmice.springbootselfexercise.controller"))
.build();
}
/**
* 构建 api文档的详细信息函数
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("现货交易")
.version("1.0.0")
.description("现货交易详情")
.contact(new Contact("geekmice","http://geekmice.cn","2437690868@qq.com"))
.build();
}
}
访问地址:http://localhost:端口号/doc.html
2.4 关于切面处理耗时
1 自定义注解
package com.geekmice.springbootselfexercise.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author PMB
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodExporter {
}
2 定义切面类
package com.geekmice.springbootselfexercise.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @BelongsProject: spring-boot-scaffold
* @BelongsPackage: com.geekmice.com.geekmice.sbpagehelper.utils
* @Author: pmb
* @CreateTime: 2023-07-26 21:52
* @Description: TODO
* @Version: 1.0
*/
@Aspect
@Component
@Slf4j
public class MethodExporterAspect {
@Around("@annotation(com.geekmice.springbootselfexercise.annotation.MethodExporter)")
public Object methodExporter(ProceedingJoinPoint point) throws Throwable {
long start = System.currentTimeMillis() / 1000;
Object proceed = point.proceed();
long end = System.currentTimeMillis() / 1000;
log.info("【耗时:】{}s", (end - start));
return proceed;
}
}
3 如何使用
直接在控制层方法上面添加注解,调用接口即可生效
@PostMapping(value = "uploadFileByEasyPoi")
@MethodExporter
@ApiOperation(value = "通过easypoi上传文件")
public AjaxResult uploadFileByEasyPoi(@RequestPart MultipartFile file){
userService.uploadFileByEasyPoi(file);
return AjaxResult.success();
}
2.5 核心导入操作
@Override
public void uploadFileByEasyPoi(MultipartFile file) {
InputStream inputStream;
try {
inputStream = file.getInputStream();
} catch (IOException e) {
log.error("error msg 【{}】", e);
throw new IllegalArgumentException(e);
}
try {
List<UserDomain> result = ExcelImportUtil.importExcel(inputStream, UserDomain.class, new ImportParams());
for (UserDomain userDomain : result) {
log.info("导入数据:【{}】", userDomain.toString());
}
this.saveBatch(result);
log.info("录入结束");
} catch (Exception e) {
log.error("error msg 【{}】", e);
throw new IllegalArgumentException(e);
}
}
2.6 核心导出操作
ExcelExportUtil是easypoi里面工具类
public void downloadFileByEasyPoi() {
List<UserDomain> list = new ArrayList(16);
for (int i = 0; i < 10; i++) {
UserDomain item = UserDomain.builder().userName(RandomStringUtils.randomAlphabetic(6)).address(RandomStringUtils.randomAlphabetic(10))
.birthday(new Date())
.sex("男")
.build();
list.add(item);
}
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), UserDomain.class, list);
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream("D:\\easypoi.xlsx");
workbook.write(outputStream);
} catch (FileNotFoundException e) {
log.error("error msg 【{}】", e);
throw new IllegalArgumentException(e);
} catch (IOException e) {
log.error("error msg 【{}】", e);
throw new IllegalArgumentException(e);
}
}
2 最佳实线
2.1 导入操作
1 实体类说明
关于注解说明
easypoi起因就是Excel的导入导出,最初的模板是实体和Excel的对应,model–row,filed–col 这样利用注解我们可以和容易做到excel到导入导出
经过一段时间发展,现在注解有5个类分别是
@Excel
作用到filed上面,是对Excel一列的一个描述
@ExcelCollection
表示一个集合,主要针对一对多的导出,比如一个老师对应多个科目,科目就可以用集合表示
@ExcelEntity
表示一个继续深入导出的实体,但他没有太多的实际意义,只是告诉系统这个对象里面同样有导出的字段
@ExcelIgnore
和名字一样表示这个字段被忽略跳过这个导导出
@ExcelTarget
这个是作用于最外层的对象,描述这个对象的id,以便支持一个对象可以针对不同导出做出不同处理
package com.geekmice.springbootselfexercise.domain;
import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelIgnore;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* (User)实体类
*
* @author pingmingbo
* @since 2023-08-06 09:51:28
*/
@Data
@ApiModel
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "user")
public class UserDomain implements Serializable {
private static final long serialVersionUID = 723356122339609354L;
/**
* 编号
*/
@ApiModelProperty(value = "编号")
@ExcelIgnore
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
@ApiModelProperty(value = "用户名")
@Excel(name = "用户名")
@TableField(value = "user_name")
private String userName;
/**
* 生日
*/
@ApiModelProperty(value = "生日")
@Excel(name="生日")
@TableField(value = "birthday")
private Date birthday;
/**
* 性别
*/
@Excel(name = "性别")
@ApiModelProperty(value = "性别")
@TableField(value = "sex")
private String sex;
/**
* 地址
*/
@ApiModelProperty(value = "地址")
@Excel(name = "地址")
@TableField(value = "address")
private String address;
}
2 业务层
public interface UserService extends IService<UserDomain> {
void uploadFileByEasyPoi(MultipartFile file);
}
package com.geekmice.springbootselfexercise.service.impl;
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 com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.geekmice.springbootselfexercise.dao.UserDao;
import com.geekmice.springbootselfexercise.domain.UserDomain;
import com.geekmice.springbootselfexercise.service.UserService;
import com.sun.corba.se.impl.corba.ExceptionListImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* (User)表服务实现类
*
* @author pingmingbo
* @since 2023-08-06 09:51:28
*/
@Service("userService")
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserDao, UserDomain> implements UserService {
@Resource
private UserDao userDao;
@Override
public void uploadFileByEasyPoi(MultipartFile file) {
InputStream inputStream;
try {
inputStream = file.getInputStream();
} catch (IOException e) {
log.error("error msg 【{}】", e);
throw new IllegalArgumentException(e);
}
try {
List<UserDomain> result = ExcelImportUtil.importExcel(inputStream, UserDomain.class, new ImportParams());
for (UserDomain userDomain : result) {
log.info("导入数据:【{}】", userDomain.toString());
}
this.saveBatch(result);
log.info("录入结束");
} catch (Exception e) {
log.error("error msg 【{}】", e);
throw new IllegalArgumentException(e);
}
}
}
3 效果
JDBC Connection [HikariProxyConnection@880330951 wrapping com.mysql.cj.jdbc.ConnectionImpl@55e295a5] will not be managed by Spring
==> Preparing: INSERT INTO user ( user_name, sex, address ) VALUES ( ?, ?, ? )
==> Parameters: pDwITO(String), 男(String), gqrkCeEpHh(String)
==> Parameters: TvOJNo(String), 男(String), BsAivNwjhP(String)
==> Parameters: miTVeL(String), 男(String), SMZOmxceID(String)
==> Parameters: siFTKf(String), 男(String), LcBfUNnITp(String)
==> Parameters: KGnyWX(String), 男(String), JXPbQboTdH(String)
==> Parameters: RyZYiE(String), 男(String), TydYPZxLah(String)
==> Parameters: JrcAnl(String), 男(String), bCttkDXCAN(String)
==> Parameters: nYMiAc(String), 男(String), uGXhBfyKgK(String)
==> Parameters: nDgzmK(String), 男(String), ZunMUZSYXa(String)
==> Parameters: xdLspA(String), 男(String), CLNrrpNHPw(String)
2023-08-06 14:00:51.218 INFO 9244 — [nio-8081-exec-3] c.g.s.service.impl.UserServiceImpl : 录入结束
3 控制层
@PostMapping(value = "uploadFileByEasyPoi")
@MethodExporter
@ApiOperation(value = "通过easypoi上传文件")
public AjaxResult uploadFileByEasyPoi(@RequestPart MultipartFile file){
userService.uploadFileByEasyPoi(file);
return AjaxResult.success();
}
2.2 导出操作
核心代码
@Override
public void downloadFileByEasyPoi() {
List<UserDomain> list = new ArrayList(16);
for (int i = 0; i < 10; i++) {
UserDomain item = UserDomain.builder().userName(RandomStringUtils.randomAlphabetic(6)).address(RandomStringUtils.randomAlphabetic(10))
.birthday(new Date())
.sex("男")
.build();
list.add(item);
}
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), UserDomain.class, list);
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream("D:\\easypoi.xlsx");
workbook.write(outputStream);
} catch (FileNotFoundException e) {
log.error("error msg 【{}】", e);
throw new IllegalArgumentException(e);
} catch (IOException e) {
log.error("error msg 【{}】", e);
throw new IllegalArgumentException(e);
}
}
2.3 大数据量处理
大数据导出是当我们的导出数量在几万,到上百万的数据时,一次从数据库查询这么多数据加载到内存然后写入会对我们的内存和CPU都产生压力,这个时候需要我们像分页一样处理导出分段写入Excel缓解Excel的压力 EasyPoi提供的是两个方法 *强制使用 xssf版本的Excel *
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.9.1</version>
</dependency>
importExcelBySax方法
/**
* Excel 通过SAX解析方法,适合大数据导入,不支持图片
* 导入 数据源本地文件,不返回校验结果 导入 字 段类型 Integer,Long,Double,Date,String,Boolean
*
* @param inputstream
* @param pojoClass
* @param params
* @param handler
*/
public static void importExcelBySax(InputStream inputstream, Class<?> pojoClass,
ImportParams params, IReadHandler handler) {
new SaxReadExcel().readExcel(inputstream, pojoClass, params, handler);
}
@Override
public void uploadBigFileByEasyPoi(MultipartFile file) {
InputStream inputStream;
try {
inputStream = file.getInputStream();
} catch (IOException e) {
log.error("error msg 【{}】", e);
throw new IllegalArgumentException(e);
}
ExcelImportUtil.importExcelBySax(inputStream,UserDomain.class,new ImportParams(),new UserHandler<UserDomain>());
}
10w条数据,35s
与正常导入,10w数据,20s
4 问题
4.1 日期类型字段特殊处理
format属性指定一下日期格式
@Excel(name="生日",format = "yyyy-MM-dd")
private Date birthday;
4.2 文件上传
修改默认上传大小
servlet:
multipart:
max-file-size: 50MB
max-request-size: 50MB
4.3 指定导出路径
核心代码
FileOutputStream outputStream = new FileOutputStream("D:\\test.xlsx"); // 指定输出流位置
workbook.write(outputStream) // 写到workbook中
业务层
List<UserDomain> list = new ArrayList(16);
for (int i = 0; i < 100000; i++) {
UserDomain item = UserDomain.builder().userName(RandomStringUtils.randomAlphabetic(6)).address(RandomStringUtils.randomAlphabetic(10))
.birthday(new Date())
.sex("男")
.build();
list.add(item);
}
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), UserDomain.class, list);
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream("D:\\easypoi.xlsx");
workbook.write(outputStream);
} catch (FileNotFoundException e) {
log.error("error msg 【{}】", e);
throw new IllegalArgumentException(e);
} catch (IOException e) {
log.error("error msg 【{}】", e);
throw new IllegalArgumentException(e);
}
log.info("easypoi导出结束");