【工具与中间件】EasyExcel 常用操作

文章目录

  • 0. 前言
  • 1. 准备工作
  • 2. 快速入门
    • 2.1 写:导出Excel
      • 2.2.1 第一个导出
      • 2.2.2 自定义表头
      • 2.2.3 Web导出(重点)
    • 2.2 读:导入Excel
      • 2.2.1 监听器读
      • 2.2.2 多Sheet情况
      • 2.2.3 Web导入(重点)
    • 2.3 填充 Excel
  • 3. 总结与补充
    • 3.1 官方代码Demo补充
    • 3.2 实际开发注意事项
    • 3.3 总结

0. 前言

在企业工作中,作为程序员,我们不可避免地或多或少需要使用Excel进行数据的导入导出。在JAVA程序中,我们常用 EasyExcel、Apache poi等框架进行数据的Excel操作。因为相比其它框架的优越的内存性能(详见下文官方文档与文档里的开源地址),许多企业优先选用 EasyExcel 进行 Excel 相关操作。本文着重介绍EasyExcel,根据官方文档,提供一些基础案例, 一起来边学边用吧!

参考文档:
EasyExcel 官方文档地址:https://easyexcel.opensource.alibaba.com/docs/current/

EasyExcel特点(总结自官方文档):
开源、效率高、使用方便、内存溢出风险低。

学习建议:
基础:JAVA语言基础(HttpServlet,IOStream)、数据库基础、一定的框架基础(Springboot 整合 SSM等)

标准:以官方文档为准,本文一些例子为个人学习过程中自己造的测试数据,以及参照官方文档自己写的例子。建议先阅读官方文档,再阅读本文。已阅读过官方文档的同学,通过本文作一个复习,可重点阅读“Web下的导入与导出”与“总结与补充”章节。

学习目标:
学会使用 EasyExcel 进行Excel 数据的导入、导出,并可将 EasyExcel 用于工作、个人项目。

1. 准备工作

依赖准备

        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>easyexcelartifactId>
            <version>3.1.0version>
        dependency>

以下准备工作为可选项,根据读者需求选用。

准备数据表

demo_table

准备测试数据:

test_data

准备实体类

/**
 * @author: Sharry
 * @createTime: 2023/3/6 14:50
 * @version: Version-1.0
 */
@Data
public class DemoData implements Serializable {

    private static final long serialVersionUID = 4194017968201414552L;

    /**
     * 主键id
     */
    private Long id;

    /**
     * 测试类型:String名字
     */
    private String name;

    /**
     * 测试类型:Date 日期
     */
    private Date date;

    /**
     * 测试类型:Double 浮点数
     */
    private Double doubleData;
}

准备MybatisPlus依赖及相关基础类

依赖:

		
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.4.2version>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>

配置类:

@Configuration
@MapperScan("cn.sharry.mplearning.mapper")
public class MybatisPlusConfig {

    /**
     * 分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

基础类:

/**
 * @author: Sharry
 * @createTime: 2023/3/6 15:22
 * @version: Version-1.0
 */
@Repository
public interface DemoDataMapper extends BaseMapper<DemoData> {
}

/**
 * @author: Sharry
 * @createTime: 2023/3/6 15:23
 * @version: Version-1.0
 */
public interface IDemoDataService extends IService<DemoData> {
}
/**
 * @author: Sharry
 * @createTime: 2023/3/6 15:24
 * @version: Version-1.0
 */
@Service
public class DemoDataServiceImpl extends ServiceImpl<DemoDataMapper, DemoData> implements IDemoDataService {
}

准备生成测试数据的方法

以下方法建议写在测试类,或者自己方便找到的位置,接下来的学习,一些数据我们都需要基于以下测试数据。其中,生成的测试数据列表来自于官方文档,在表中直接查到的测试数据与生成的数据列表二选一用。

 	/**
     * 通用测试数据
     */
    private List<DemoData> data() {
        List<DemoData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setName("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        return list;
    }

	@Autowired
    private IDemoDataService demoDataService; 	
	/**
     * 获取表内的测试数据
     */
    public List<DemoData> getTestData(){
        return demoDataService.list();
    }	

Hutool工具包与Lombok(如果跟着本文操作则需要)

		
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>5.8.14version>
        dependency>
		
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>			

注意,以上准备工作均需要基于一个JAVA Maven工程(最好是SSM)。急需使用的同学,需要先看看公司所使用的框架是否已经集成EasyExcepl相关依赖。

2. 快速入门

提到导入与导出,在学习 EasyExcel 之前,我们首先会想到JAVA中的文件流。通过文件流,我们可以实现数据的文件读写。而Excel也是一种以".xlsx" 结尾的文件,因此我们使用EasyExcel,也会用到一些流的东西,整个导入导出流程,也和操作文件流类似。

2.1 写:导出Excel

2.2.1 第一个导出

我们先从导出Excel开始。

要导出Excel,我们首先要将实体类加上EasyExcel相关注解

/**
 * @author: Sharry
 * @createTime: 2023/3/6 14:50
 * @version: Version-1.0
 */
@Data
@TableName("excel_demo_data")
public class DemoData implements Serializable {

    private static final long serialVersionUID = 4194017968201414552L;

    /**
     * 主键id
     */
    @ExcelProperty("主键ID")
    private Long id;

    /**
     * 测试类型:String名字
     */
    @ExcelProperty("字符串标题Name")
    private String name;

    /**
     * 测试类型:Date 日期
     */
    @ExcelProperty("日期标题Date")
    private Date date;

    /**
     * 测试类型:Double 浮点数
     */
    @ExcelProperty("浮点数标题DoubleData")
    private Double doubleData;

    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    @TableField(exist = false)
    private String ignore;
}

测试类:

	/**
     * 测试简单写操作,导出Excel
     */
    @Test
    public void testSimpleWrite(){
        //定义文件路径
        String fileName =  "E:\\DPlus\\Miscellaneous\\"+System.currentTimeMillis()+".xlsx";
        log.trace("文件路径为{}",fileName);
        //根据官方文档,以下操作会自动关闭文件流
        //写法1
        EasyExcel.write(fileName, DemoData.class)
                .sheet("模板")
                .doWrite(this::getTestData);
        //写法2
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(getTestData());
        //写法3
        try (ExcelWriter excelWriter = EasyExcel.write(fileName,DemoData.class).build()){
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            excelWriter.write(data(), writeSheet);
        }
    }

此处参照的是官方文档里提供的3种写法例子,读者可通过测试类选取测试,查看效果。此处案例的Excel会导出到我们定义的本地文件路径。

导出结果:
【工具与中间件】EasyExcel 常用操作_第1张图片

至此,我们完成了第一个导出!Hello EasyExcel !

2.2.2 自定义表头

在程序中定义表头的宽高、表头字段是常用的方法。有了第一个导出的基础,我们直接举例:


/*
* 以下涉及宽高的注解,为定义Excel表字段宽高的注解
*/
@Data
@ColumnWidth(25)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class DemoData implements Serializable {
       
    private static final long serialVersionUID = 4194017968201414552L;
    
    /**
     * 主键id
     */
    @ColumnWidth(15)
    @ExcelProperty("主键ID")
    private Long id;

    /**
     * 测试类型:String名字
     */
    @ColumnWidth(10)
    @ExcelProperty("字符串标题Name")
    private String name;

    /**
     * 测试类型:Date 日期
     */
    @ColumnWidth(20)
    @ExcelProperty("日期标题Date")
    private Date date;

    /**
     * 测试类型:Double 浮点数
     */
    @ColumnWidth(10)
    @ExcelProperty("浮点数标题DoubleData")
    private Double doubleData;

    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    @ColumnWidth(30)
    @TableField(exist = false)
    private String ignore;

}
@Test
    public void dynamicHeadWrite() {
        //定义文件路径
        String fileName =  "E:\\DPlus\\Miscellaneous\\"+System.currentTimeMillis()+".xlsx";
        EasyExcel.write(fileName)
                 // 动态头
                .head(head()).sheet("模板")
                .doWrite(data());
    }

    private List<List<String>> head() {
        List<List<String>> list = new ArrayList<>();
        List<String> head0 = new ArrayList<>();
        head0.add("字符串");
        List<String> head1 = new ArrayList<>();
        head1.add("数字");
        List<String> head2 = new ArrayList<>();
        head2.add("日期");
        list.add(head0);
        list.add(head1);
        list.add(head2);
        return list;
    }

2.2.3 Web导出(重点)

实际工作中大部分导入导出Excel的需求都要求在Web端完成,此处是重点。同时,有了以上导出的基础,此处直接举例,读者应该易于理解:

/**
 * @author: Sharry
 * @createTime: 2023/3/7 11:48
 * @version: Version-1.0
 */
@Slf4j
@RestController
@RequestMapping("/excel")
public class EasyExcelController {

    @Autowired
    private IDemoDataService demoDataService;

    /**
     * 导出Excel,代码参考自官方文档
     */
    @GetMapping("/download")
    public void download(HttpServletResponse response) throws IOException {
        // 直接用浏览器或postman测试
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        
        response.setCharacterEncoding("utf-8");
        // 防止中文乱码 
        String fileName 
            = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
        
        response.setHeader(
            "Content-disposition", 
            "attachment;filename*=utf-8''" 
            + fileName 
            + ".xlsx");
        
        EasyExcel.write(response.getOutputStream(), DemoData.class)
            .sheet("模板")
            .doWrite(demoDataService.list());
    }
}

2.2 读:导入Excel

2.2.1 监听器读

创建监听器类:

/**
 * 读 Excel 监听器
 * @author: Sharry
 * @createTime: 2023/3/6 16:44
 * @version: Version-1.0
 */
@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {
    /**
     * 参照官方文档定义批量常量、缓存依据
     */
    private static final int BATCH_COUNT = 100;

    /**
     * 缓存数据
     */
    private List<DemoData> cacheDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    private IDemoDataService demoDataService;

    /**
     * 有参构造,参照官方文档,使用有Spring的方式
     */
    public DemoDataListener(IDemoDataService demoDataService){
        this.demoDataService = demoDataService;
    }

    /**
     * 参考官方文档
     * 每一条数据解析时都会调用
     * @param demoData one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param analysisContext 分析文本
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void invoke(DemoData demoData, AnalysisContext analysisContext) {
        log.trace("解析到一条数据{}", JSONUtil.toJsonStr(demoData));
        cacheDataList.add(demoData);
        //达到 BATCH_COUNT , 执行持久化操作
        if(cacheDataList.size() >= BATCH_COUNT){
            log.info("{}条数据开始持久化!",cacheDataList.size());
            boolean success = demoDataService.saveBatch(cacheDataList);
            log.info("插入数据是否成功:{}",success);
        }
    }

    /**
     * 所有数据解析完成了,都会来调用
     * @param analysisContext 分析文本
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        //最后遗留的数据也要存储到数据库
        log.info("{}条数据开始持久化!",cacheDataList.size());
        boolean success = demoDataService.saveOrUpdateBatch(cacheDataList);
        log.info("插入数据是否成功:{}",success);
    }
}

这个监听器类是根据官方文档创建的,在监听器内,我们重写了Invoke和 doAfterAllAnalysed方法并提供了持久化方法。最终读取结果可以在数据库里显示

准备数据
使用上文提到的数据准备方法,生成数据

【工具与中间件】EasyExcel 常用操作_第2张图片

测试方法

/**
     * 测试简单的读操作,导入Excel
     */
    @Test
    public void testSimpleRead(){
        //定义文件路径
        String fileName =  "E:\\DPlus\\Miscellaneous\\"+"1678149812045"+".xlsx";
        //写法1 通过 listener 导入
        try (ExcelReader excelReader = EasyExcel.read(
                fileName,
                DemoData.class,
                new DemoDataListener(demoDataService)).build()){
                ReadSheet readSheet = EasyExcel.readSheet(0).build();
                excelReader.read(readSheet);
        }
    }

结果

【工具与中间件】EasyExcel 常用操作_第3张图片

其它写法举例
我们可以选用以下写法,或者匿名内部类来实现相同的功能。

EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(
                dataList -> dataList.forEach(
                    e ->log.info("读取到一条数据{}", JSONUtil.toJsonStr(e)))
        )).sheet().doRead();

2.2.2 多Sheet情况

什么是Sheet?
【工具与中间件】EasyExcel 常用操作_第4张图片

Sheet是指"工作表",excel里的概念,一个xlsx文件里可以有很多Sheet。多Sheet的情况相当于,一下把多张表的数据一起导入了,通过Sheet区分。我们实操看看:

@Test
    public void testReadSheet(){
        // 定义文件路径
        String fileName =  "E:\\DPlus\\Miscellaneous\\"+"1678149812045"+".xlsx";
        
        // 以下分别执行测试
        
        // 读取全部 Sheet
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener(demoDataService)).doReadAll();

        // 读取指定 Sheet
        try(ExcelReader excelReader = EasyExcel.read(fileName).build()){
            ReadSheet sheet1 = EasyExcel
                    .readSheet(0)
                    .head(DemoData.class)
                    .registerReadListener(new DemoDataListener(demoDataService))
                    .build();

            ReadSheet sheet2 = EasyExcel
                    .readSheet(1)
                    .head(DemoData.class)
                    .registerReadListener(new DemoDataListener(demoDataService))
                    .build();

            excelReader.read(sheet1,sheet2);
        }

    }

2.2.3 Web导入(重点)

作为JAVA后端码农,我们导入导出excel的大部分需求都是在Web环境下进行的,以下直接举例Web导入:

/**
 * 上传Excel,代码参考自官方文档
 */
@PostMapping("/upload")
public JsonResult<Void> upload(MultipartFile file) throws IOException {
    EasyExcel.read(file.getInputStream(),
            DemoData.class,
            new DemoDataListener(demoDataService))
            .sheet().doRead();
    return JsonResult.success();
}

通过 postman 测试结果:

【工具与中间件】EasyExcel 常用操作_第5张图片

2.3 填充 Excel

填充 Excel 也是常见的需求之一,在给用户导出 Excel 时,有一种解决方案便是给定一个模板,然后填充模板,再给用户下载。

这里我们直接参考官方文档的例子:

实体类

/**
 * @author Sharry
 */
@Data
public class FillData implements Serializable {
    private static final long serialVersionUID = 1273825356454488497L;
    private String name;
    private double number;
    private Date date;
}

准备模板

【工具与中间件】EasyExcel 常用操作_第6张图片

测试类

@Test
public void simpleFill() {
    // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替
    // 定义文件路径
    String templateFileName = "E:\\DPlus\\Miscellaneous\\"+"1678149812045"+".xlsx";

    // 方案1 根据对象填充
    String fileName = "E:\\DPlus\\Miscellaneous\\"+System.currentTimeMillis()+".xlsx";
    // 这里 会填充到第一个sheet, 然后文件流会自动关闭
    FillData fillData = new FillData();
    fillData.setName("张三");
    fillData.setNumber(5.2);
    fillData.setDate(new Date(System.currentTimeMillis()));
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);

    // 这里 会填充到第一个sheet, 然后文件流会自动关闭
    Map<String, Object> map = MapUtils.newHashMap();
    map.put("name", "张三");
    map.put("number", 5.2);
    fillData.setDate(new Date(System.currentTimeMillis()));
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);
}

3. 总结与补充

本文只为读者入门或快速复习EasyExcel,提供了最常用、最基本的例子。官方还提供了不同情况下的Excel导入导出Demo(详见官方文档),大多都大同小异。

在下面的小节做一个简单的补充说明。

3.1 官方代码Demo补充

通过上面章节的学习,我们已经了解了 EasyExcel 的一些基本使用,以下链接为官方提供的Demo代码链接,供读者参考:

官方读Excel示例代码:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java

官方写Excel示例代码:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java

官方填充Excel示例代码:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java

3.2 实际开发注意事项

我们在实际开发中,为了提升效率,我们通常会使用一些框架。在市面上的商用框架中,许多框架已经对EasyExcel进行了一步封装,此时,我们就要遵循框架封装过后的使用规则。举个例子:

@PostMapping("write-notice")
public R<Boolean> writeNotice(MultipartFile file) {
   List<Notice> noticeList = new ArrayList<>();
   List<NoticeExcel> list = ExcelUtil.read(file, NoticeExcel.class);
   list.forEach(noticeExcel -> {
      String category = DictCache.getKey("notice", noticeExcel.getCategoryName());
      noticeExcel.setCategory(Func.toInt(category));
      Notice notice = BeanUtil.copy(noticeExcel, Notice.class);
      noticeList.add(notice);
   });
   return R.data(noticeService.saveBatch(noticeList));
}

上述代码提到的 “ExcelUtil” 就是框架已经封装好的一个Excel工具,不同的框架的封装方式可能不同,实际开发中注意使用。

3.3 总结

在学习和使用EasyExcel过程中,我们不难发现,其实Excel相关的操作大部分情况都是基于流的。作为JAVA语言基础之一,我们应该对流多加学习、复习、练习。

最后,我们一起来梳理一下实际工作中可能会用到的EasyExcel情况及使用方法:

  1. 环境准备:相关依赖
  2. 根据需求,准备Excel模板(可选)、创建实体类并添加相应注解
  3. 我们用得最多的就是Web下的导入与导出,重点关注官方和其它各种参考文档的Web下Excel导入导出案例,参考着编写即可
  4. 一般情况下,由于EasyExcel已帮我们进行了封装,我们不需要手动开关流,但是要注意数据量大的情况下,我们还是要注意尽量使用监听器的方式读Excel,防止内存溢出
  5. 使用得较少的导入导出情况,例如需要操作批注信息、公式等,可边做边参考官方及其它参考文档

你可能感兴趣的:(工具与中间件,中间件,java,excel)