EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。
源码:https://github.com/alibaba/easyexcel
文档:https://easyexcel.opensource.alibaba.com
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version> <!--3.0.6 Error:无效的源发行版:13-->
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lmy</groupId>
<artifactId>qy163-easyexcel1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>qy163-easyexcel1</name>
<description>qy163-easyexcel1</description>
<properties>
<java.version>8</java.version> <!--17 java: 错误: 无效的源发行版:17-->
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--easyexcel依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
<!--easyexcel依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
package com.lmy.excel;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
/*写Excel-->最简单的写-->最简单的写的对象*/
public class ExcelDemo {
@ExcelProperty(value = "编号") //value 表示标头
private Integer id;
@ExcelProperty(value = "姓名") //value 表示标头
private String name;
@ExcelProperty(value = "年龄") //value 表示标头
private Integer age;
@ExcelIgnore //表示该属性的值不会被写入到excel
private String address;
}
package com.lmy.excel;
import com.alibaba.excel.EasyExcel;
import java.util.ArrayList;
import java.util.List;
/*写Excel-->最简单的写-->代码-->写法2*/
public class TestWrite1 {
public static void main(String[] args) {
//excel 存在哪个位置
String fileName = "E:\\JavaWork\\qy163-easyexcel1\\qy163.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
List<ExcelDemo> data = new ArrayList<>();
data.add(new ExcelDemo(1,"喜羊羊",10,"羊村"));
data.add(new ExcelDemo(2,"美羊羊",5,"羊村"));
data.add(new ExcelDemo(3,"熊大",18,"狗熊岭"));
data.add(new ExcelDemo(4,"熊二",15,"狗熊岭"));
EasyExcel.write(fileName, ExcelDemo.class).sheet("lmy").doWrite(data);
}
}
package com.lmy.controller;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.fastjson.JSON;
import com.lmy.excel.ExcelDemo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Controller
/*写Excel-->web中的写并且失败的时候返回json-->代码*/
public class UploadController {
@GetMapping("/upload")
public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
List<ExcelDemo> data = new ArrayList<>();
data.add(new ExcelDemo(1,"喜羊羊",10,"羊村"));
data.add(new ExcelDemo(2,"美羊羊",5,"羊村"));
data.add(new ExcelDemo(3,"熊大",18,"狗熊岭"));
data.add(new ExcelDemo(4,"熊二",15,"狗熊岭"));
upload(response,"qy163五组学员信息222",data);
}
public void upload(HttpServletResponse response,String title,List<?> data) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
try {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode(title, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
// 这里需要设置不关闭流
EasyExcel.write(response.getOutputStream(), ExcelDemo.class).autoCloseStream(Boolean.FALSE).sheet("模板")
.doWrite(data);
} catch (Exception e) {
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Map<String, String> map = MapUtils.newHashMap();
map.put("status", "failure");
map.put("message", "下载文件失败" + e.getMessage());
response.getWriter().println(JSON.toJSONString(map));
}
}
}
package com.lmy.excel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
// 有个很重要的点 ExcelDemoListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
/*读Excel-->最简单的读-->最简单的读的监听器*/
public class ExcelDemoListener implements ReadListener<ExcelDemo> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<ExcelDemo> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private DemoDAO demoDAO;
public ExcelDemoListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
demoDAO = new DemoDAO();
}
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param demoDAO
*/
public ExcelDemoListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(ExcelDemo data, AnalysisContext context) {
log.info("解析到一条数据:{}", JSON.toJSONString(data));
cachedDataList.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
log.info("~~~~~~~~~~~~~~~~~~~~~~~~");
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
demoDAO.save(cachedDataList);
log.info("存储数据库成功!");
}
}
package com.lmy.excel;
import java.util.List;
/**
* 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。
**/
/*读Excel-->最简单的读-->持久层*/
public class DemoDAO {
public void save(List<ExcelDemo> list) {
// 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
System.out.println("保存数据到数据库=====批量保存");
}
}
package com.lmy.excel;
import com.alibaba.excel.EasyExcel;
public class TestRead1 {
public static void main(String[] args) {
// 写法3:
String fileName = "E:\\JavaWork\\qy163-easyexcel1\\qy163.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, ExcelDemo.class, new ExcelDemoListener()).sheet().doRead();
}
}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql:///qy163?serverTimezone=Asia/Shanghai
package com.lmy.pojo;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("tbl_user")
public class User {
@TableId
@ExcelProperty("编号")
private int id;
@ExcelProperty("姓名")
private String name;
@ExcelProperty("年龄")
private Integer age;
}
package com.lmy.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lmy.pojo.User;
import java.util.List;
public interface UserDao extends BaseMapper<User> {
void batchSave(List<User> cachedDataList);
}
package com.lmy.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import com.lmy.pojo.User;
import com.lmy.dao.UserDao;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
// 有个很重要的点 UserListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class UserDataListener implements ReadListener<User> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<User> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private UserDao userDao;
public UserDataListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
//userDao = new UserDao();
}
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param userDao
*/
public UserDataListener(UserDao userDao) {
this.userDao = userDao;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(User data, AnalysisContext context) {
log.info("解析到一条数据:{}", JSON.toJSONString(data));
cachedDataList.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
log.info("~~~~~~~~~~~~~~~~~~~~~~~~");
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
userDao.batchSave(cachedDataList);
log.info("存储数据库成功!");
}
}
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lmy.dao.UserDao">
<insert id="batchSave">
insert into tbl_user values
<foreach collection="list" item="user" separator=",">
(#{user.id},#{user.name},#{user.age})
foreach>
insert>
mapper>
package com.lmy;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.lmy.dao")
public class Qy163Easyexcel1Application {
public static void main(String[] args) {
SpringApplication.run(Qy163Easyexcel1Application.class, args);
}
}
package com.lmy;
import com.alibaba.excel.EasyExcel;
import com.lmy.dao.UserDao;
import com.lmy.listener.UserDataListener;
import com.lmy.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Qy163Easyexcel1ApplicationTests {
@Autowired
UserDao userDao;
@Test
void contextLoads() {
// 写法3:
String fileName = "E:\\JavaWork\\qy163-easyexcel1\\qy163.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, User.class, new UserDataListener(userDao)).sheet().doRead();
}
}
package com.lmy.controller;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.fastjson.JSON;
import com.lmy.dao.UserDao;
import com.lmy.excel.ExcelDemo;
import com.lmy.listener.UserDataListener;
import com.lmy.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Controller
/*写Excel-->web中的写并且失败的时候返回json-->代码*/
public class UploadController {
/**
* 文件上传
*
* 1. 创建excel对应的实体对象 参照{@link User}
*
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UserDataListener}
*
* 3. 直接读即可
*/
@Autowired
private UserDao userDao;
@PostMapping("upload2")
@ResponseBody
public String upload2(MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(), User.class, new UserDataListener(userDao)).sheet().doRead();
return "success";
}
@GetMapping("/upload")
public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
List<ExcelDemo> data = new ArrayList<>();
data.add(new ExcelDemo(1,"喜羊羊",10,"羊村"));
data.add(new ExcelDemo(2,"美羊羊",5,"羊村"));
data.add(new ExcelDemo(3,"熊大",18,"狗熊岭"));
data.add(new ExcelDemo(4,"熊二",15,"狗熊岭"));
upload(response,"qy163五组学员信息",data);
}
public void upload(HttpServletResponse response,String title,List<?> data) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
try {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode(title, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
// 这里需要设置不关闭流
EasyExcel.write(response.getOutputStream(), ExcelDemo.class).autoCloseStream(Boolean.FALSE).sheet("模板")
.doWrite(data);
} catch (Exception e) {
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Map<String, String> map = MapUtils.newHashMap();
map.put("status", "failure");
map.put("message", "下载文件失败" + e.getMessage());
response.getWriter().println(JSON.toJSONString(map));
}
}
}