java在项目中实现excel导入导出

news2024/9/29 23:59:10

一、初识EasyExcel*

1. Apache POI

先说POI,有过报表导入导出经验的同学,应该听过或者使用。

Apache POI是Apache软件基金会的开源函式库,提供跨平台的Java API实现Microsoft Office格式档案读写。但是存在如下一些问题:

1.1 学习使用成本较高

对POI有过深入了解的才知道原来POI还有SAX模式(Dom解析模式)。但SAX模式相对比较复杂,excel有03和07两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。

想要了解清楚这两种解析方式,才去写代码测试,估计两天时间是需要的。再加上即使解析完,要转换到自己业务模型还要很多繁琐的代码。总体下来感觉至少需要三天,由于代码复杂,后续维护成本巨大。

POI的SAX模式的API可以一定程度的解决一些内存溢出的问题,但是POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大,一个3M的Excel用POI的SAX解析,依然需要100M左右内存。

1.2 POI的内存消耗较大

大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。

总体上来说,简单写法重度依赖内存,复杂写法学习成本高。

特点
  1. 功能强大

  2. 代码书写冗余繁杂

  3. 读写大文件耗费内存较大,容易OOM

2. EasyExcel

2.1 重写了POI对07版Excel的解析
  • EasyExcel重写了POI对07版Excel的解析,可以把内存消耗从100M左右降低到10M以内,并且再大的Excel不会出现内存溢出,03版仍依赖POI的SAX模式。

  • 下图为64M内存1分钟内读取75M(46W行25列)的Excel(当然还有急速模式能更快,但是内存占用会在100M多一点)

img

  • 在上层做了模型转换的封装,让使用者更加简单方便
特点
  1. 在数据模型层面进行了封装,使用简单
  2. 重写了07版本的Excel的解析代码,降低内存消耗,能有效避免OOM
  3. 只能操作Excel
  4. 不能读取图片

二、快速入门–QuickStart

0、导入依赖坐标

<!-- EasyExcel -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.6</version>
</dependency>
<!-- lombok 优雅编程 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.20</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

导入easyexcel-2.1.6坐标的时候,已依赖传递导入poi-3.17的POI。

1、最简单的读

1.1、需求、准备工作
/**
 * 需求:单实体导入(从磁盘将文件导入到应用程序)
 * 导入Excel学员信息到系统。
 * 包含如下列:姓名、性别、出生日期
 * 模板详见:学员信息.xlsx
 */

学员信息.xlsx

image-20230618174203589

1.2、编写导出数据的实体类
package com.taotie.test;

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
// 基于lombok
@Data
@NoArgsConstructor
@AllArgsConstructor
@HeadRowHeight(20) // 指定列头行高
@ColumnWidth(20) // 指定列宽
public class Student {
    /**
     * 学生姓名
     */
    @ExcelProperty(value = "学生姓名", index = 0)
    private String name;
    /**
     * 学生性别
     */
    @ExcelProperty(value = "学生性别", index = 2)
    private String gender;

    /**
     * 学生出生日期
     */
    @ExcelProperty(value = "学生出生日期", index = 1)
    private Date birthday;
    /**
     * id
     */
    // @ExcelProperty(value = "编号",index = 3)
    @ExcelIgnore // 忽略,不读取
    private String id;
}

注解: 文章后面有详解

1.3、 读取Excel文件(上传)

调用EasyExcelAPI读取的Excel文件的测试类StudentReadDemo

package com.taotie.test;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
@SpringBootTest 
public class TestEasyExcel {

    /**
     * 测试读取数据
     */
    @Test
    public void testRead() {
        // 读取文件,读取完之后会自动关闭
        /**
         * 参数1:pathName  文件路径;"d:\\学员信息.xls"
         * 参数2:head		 每行数据对应的实体;Student.class
         * 参数3:readListener	读监听器,每读一样就会调用一次该监听器的invoke方法
         * 参数4:sheet方法参数: 工作表的顺序号(从0开始)或者工作表的名字,不传默认为0
         */
        // // 封装工作簿对象
        // ExcelReaderBuilder workBook = EasyExcel.read
        //         ("E:\\学员信息.xlsx",
        //                 Student.class,
        //                 new StudentReadListener( ));
        // // 封装工作表
        // ExcelReaderSheetBuilder sheet1 = workBook.sheet( );
        // // 读取
        // sheet1.doRead( );
        
        // 最简单的写法
        EasyExcel.read("E:\\学员信息.xlsx",Student.class,new StudentReadListener()).sheet().doRead();
    }
}

读取Excel的监听器,用于处理读取产生的数据

package com.taotie.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.taotie.domain.Student;

/**
 * @Author Vsunks.v
 * @Description:
 */
public class StudentReadListener extends AnalysisEventListener<Student> {
    // 每读一行,会调用该invoke方法一次
    @Override
    public void invoke(Student data, AnalysisContext context) {
        System.out.println("data = " + data);
    }

    // 全部读完之后,会调用该方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // TODO......
    }
}

2、最简单的写(导出)

2.1 需求、准备工作
/**
 * 需求:单实体导出(从应用程序将文件导入到磁盘)
 * 导出多个学生对象到Excel表格
 * 包含如下列:姓名、性别、出生日期
 * 模板详见:学员信息.xlsx
 */
2.2、编写导出数据的实体
// 还是上个实体类...
2.3、 准备数据并写入到文件
    /**
     * 测试写出数据
     */
    @Test
    public void simpleWrite() {
        // 创造数据
        ArrayList<Student> list = new ArrayList<>( );
        list.add(new Student("张三","男",new Date(),"1001"));
        list.add(new Student("李四","女",new Date(),"1002"));
        list.add(new Student("王五","男",new Date(),"1003"));
        list.add(new Student("赵六","女",new Date(),"1004"));
        list.add(new Student("周期","男",new Date(),"1005"));
        list.add(new Student("茅十八","女",new Date(),"1006"));

        // 写出的文件路径,当前项目下
        String fileName = "student.xlsx";

        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, Student.class).sheet("学生信息").doWrite(list);
        System.out.println("导出OK" );
    }

image-20230618182323185

image-20230618182345134

三、vue文件上传和下载[重点]

基于springboot的文件上传和下载

0. 导入依赖

<!-- EasyExcel -->

<!-- lombok -->

<!-- junit -->

3.1 文件上传

注意: 本地Excel表格,列要和数据库一致

需求: 批量插入计量单位(导入excel数据到项目中)

思路:

  • 前端设计文件上传组件 ,点击开始文件上传
  • 后端接收文件,使用工具解析数据
  • 插入数据库

3.1.1 前端

在页面设计上传的组件

1.设置上传按钮,显示上传的对话框

2.设置对话框+上传组件

image-20240726113742898

  <!-- 上传excel的对话框 -->
  <el-dialog title="上传计量单位Excel" :visible.sync="dialogExcelVisible" width="40%">
    <el-upload
      class="upload-demo"
      drag
      action="http://localhost:8888/md/unit/upload/excel"
      accept=".xlsx,.xls"
      :on-success="uploadExcelSuccess"
      :on-error="uploadExcelError"
      multiple>
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      <div class="el-upload__tip" slot="tip">只能上传.xlsx,.xls文件,且不超过500kb</div>
    </el-upload>
  </el-dialog>


uploadExcelSuccess(){
  this.$message({
    type:"success",
    message:"上传成功"
  })
  this.dialogExcelVisible = false;
  this.fetchData();
},
uploadExcelError(err){
  this.$message({
    type:"error",
    message:err
  })
}
3.1.2 后端-实体类注解

image-20240726113906412

3.1.3 后端-监听器
package com.qf.config;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.qf.entity.SysUser;
import com.qf.entity.UnitMeasure;
import com.qf.service.SysUserService;
import com.qf.service.UnitMeasureService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.util.ArrayList;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
@Component
@Scope("prototype")    // 作者要求每次读取都要使用新的Listener
public class MdUnitMeasureReadListener extends AnalysisEventListener<UnitMeasure> {
    // 有个很重要的点 DemoDataListener 不能被spring管理,
    // 要每次读取excel都要new,然后里面用到spring可以构造方法传进去
    private UnitMeasureService service;
    public MdUnitMeasureReadListener(){} // 空参构造

    // 有参构造
    public MdUnitMeasureReadListener(UnitMeasureService service){
        this.service = service;
    }

    // 每隔5条存储数据库,实际项目中使用时可以100条,然后清理list ,方便内存回收
    private final int BATCH_COUNT = 5;
    private ArrayList<UnitMeasure> list = new ArrayList<>( );

    // 每读一行,会调用该invoke方法一次
    @Override
    public void invoke(UnitMeasure data, AnalysisContext context) {
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size( ) >= BATCH_COUNT) {
            // 调用方法,执行插入
            saveData( );
            // 存储完成清理 list
            list.clear();
        }
    }

    // 全部读完之后,会调用该方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        if(list.size() > 0) {
            saveData( );
        }
        System.out.println("所有数据解析完成!");
    }


    // 向数据库插入数据
    private void saveData() {
        System.out.println("开始存储数据库!");

        // 调用业务层存数据库
        service.saveBatch(list);
        System.out.println("存储数据库成功!");
    }
}
3.1.4 后端-Controller接收
 /**
     * 上传Excel文件
     */
    @PostMapping("/upload/excel")
    public R uploadExcel(MultipartFile file) {
        try {
            // 重点,此处第三个参数需要传入Service对象
            EasyExcel.read(file.getInputStream( ),
                            UnitMeasure.class,
                            new MdUnitMeasureReadListener(service))
                    .sheet( ).doRead( );
        }catch (Exception e){
            e.printStackTrace();
            return R.fail(e.getMessage());
        }
        return R.ok( );
    }
3.1.5 后端-Service
// 需要添加批量插入的方法
3.1.6 后端-Mapper
       <!-- 批量插入 -->
    <insert id="saveBatch">
        insert into
            md_unit_measure       (measure_code,measure_name,primary_flag,primary_id,change_rate,enable_flag)
        values
        <foreach collection="list" item="unit" separator=",">
            (#{unit.measureCode},
            #{unit.measureName},
            #{unit.primaryFlag},
            #{unit.primaryId},
            #{unit.changeRate},
            #{unit.enableFlag})
        </foreach>
3.1.7 测试

本地创建一个excel表格,按照实体类中定义的列名填充数据

点击上传

image-20231220105443796

3.2 文件导出

需求: 将项目中的数据导出到本地excel表格

思路:

  • 前端设计按钮,点击开始导出
  • 后端请求,查询出数据,
  • 使用工具导出

实体类于之前一样

// 略

Controller层

/**
 * 下载Excel文件
 * 注意,返回值是void
 */
@GetMapping("/download/excel")
public void downloadExcel(HttpServletResponse response) throws IOException {

    //  查询数据库全部数据
    List<UnitMeasure> list = service.findAll(null);

    // 下面这个注释是前端使用方式1,即a标签发请求时采用
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding("utf-8");
    // 这里URLEncoder.encode可以防止中文乱码
    String fileName = URLEncoder.encode("计量单位信息", "UTF-8");
    response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");

    // easyexcel导出工具
    EasyExcel.write(response.getOutputStream(), UnitMeasure.class).autoCloseStream(Boolean.FALSE).sheet("计量单位信息")
        .doWrite(list);
}

// todo servicce+mapper实现查询全部

前端

1.导出按钮触发函数

image-20240726145525475

2.函数内确认导出,发出请求到后端

image-20240726145608991

3.3、自定义单元格样式

EasyExcel支持调整行高、列宽、背景色、字体大小等内容,但是控制方式与使用原生POI无异,比较繁琐,不建议使用。

但是可以使用模板填充的方式,向预设样式的表格中直接写入数据,写入数据的时候会保持原有样式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2087642.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言阴阳迷宫

目录 开头程序程序的流程图程序游玩的效果下一篇博客要说的东西 开头 大家好&#xff0c;我叫这是我58。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <Windows.h> enum WASD {W…

【传输层协议】TCP协议(上) {TCP协议段格式;确认应答机制;超时重传机制;连接管理机制:三次握手、四次挥手}

TCP&#xff08;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的、基于字节流的传输层协议&#xff0c;用于在网络上可靠地传输数据。TCP是互联网协议套件&#xff08;TCP/IP&#xff09;中的一个主要协议&#xff0c;它在IP&#xff08;Internet Protocol…

基于STM32开发的智能恒温系统

目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 系统初始化温度检测与恒温控制OLED显示与状态提示Wi-Fi通信与远程监控应用场景 家庭环境的智能恒温管理实验室或工业环境的精确温度控制常见问题及解决方案 常见问题解决方案结论 1. 引言 智…

VIVADO自定义 IP封装

简介 本章节主要针对VIVAO 2020.2版本做IP自定义封装&#xff0c;其中涉及到IP寄存器读写配置&#xff0c;自定义接口封装等介绍。 IP封装 IP标准自定义步骤一般有创建工程&#xff0c;封装IP&#xff0c;自定义内容&#xff0c;添加自定义库这4个步骤&#xff0c;下面…

探秘紫白洋桔梗花语:勇气、爱情、希望与清晰的象征解读

在缤纷多彩的花卉世界中&#xff0c;紫白洋桔梗宛如一位神秘而优雅的仙子&#xff0c;悄然绽放着独特的魅力。它那淡雅的色彩与别致的花形&#xff0c;令人一见倾心&#xff0c;而其背后蕴含的丰富花语&#xff0c;更是如同隐藏的宝藏一般&#xff0c;等待着我们去细细探寻与解…

蓝花楹花语探秘:从宁静忧郁到等待爱情的深刻寓意

在时光的长河中&#xff0c;有一种花朵宛如梦幻的精灵&#xff0c;每当它绽放之时&#xff0c;那一片绚烂的蓝紫色便如同璀璨的星空倾洒人间&#xff0c;它就是蓝花楹。蓝花楹那独特的身姿和醉人的色彩&#xff0c;仿佛自带一种神秘的魔力&#xff0c;吸引着无数人驻足凝望。而…

vue3+ts+vite+electron+electron-store+electron-builder打包可安装包

yarn create vite yarn add electron yarn add electron-store yarn add electron-builder 新增main.js、preload.js // main.js const { app, BrowserWindow, ipcMain, globalShortcut } require(electron) const path require(path) let store // 我们将在稍后动态导入 el…

Flink优化之--旁路缓存和异步IO

Apache Flink 是一个开源流处理框架&#xff0c;以其高吞吐量、低延迟和事件驱动的处理能力著称。随着大数据和实时处理需求的不断增加&#xff0c;Flink 在许多行业和应用场景中得到了广泛应用&#xff0c;如金融风控、物联网数据处理、实时数据分析等。然而&#xff0c;随着数…

如何学习Linux性能优化?

你是否也曾跟我一样&#xff0c;看了很多书、学了很多Linux性能工具&#xff0c;但在面对Linux性能问题时&#xff0c;还是束手无策&#xff1f;实际上&#xff0c;性能分析和优化始终是大多数软件工程师的一个痛点。但是&#xff0c;面对难题&#xff0c;我们真的就无解了吗&a…

2.11键盘事件

目录 实验原理 实验代码 实验结果 实验原理 简单、常用的键盘事件是等待按键事件&#xff0c;它由 waitKey 函数来实现。无论是刚开始学习 OpenCV&#xff0c;还是使用 OpenCV 进行开发调试&#xff0c;都可以看到waitKey 函数的身影&#xff0c;然而基础的东西往往容易忽略…

[Java]MyBatis轻松拿下

介绍 在业务开发过程中, 都是使用java程序完成数据库的操作, 目前最主流的技术就是MyBatis MyBatis是一款优秀的 持久层 框架&#xff0c;用于简化JDBC的开发。 官网: https://mybatis.org/mybatis-3/zh/index.htmlmybatis是Apache的一个开源项目iBatis, 2010年迁移到了googl…

Day00_场景题

文章目录 资料项目经历技能清单自我介绍QPS和TPS?如何设计一个排行榜的功能?如何解决大文件上传问题延时任务处理场景如何设计一个秒杀系统?分布式幂等性如何设计?如果你的系统的QPS 突然提升10倍你会怎么设计?如何从零搭建 10 万级QPS 大流量、高并发优惠券系统?高 QPS,…

OpenCV绘图函数(9)填充多边形函数fillPoly()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 填充一个或多个多边形轮廓所包围的区域。 函数 cv::fillPoly 填充由若干个多边形轮廓所包围的区域。该函数可以填充复杂的区域&#xff0c;例如带…

【大模型】GPT系列模型基础

前言&#xff1a;GPT整体上与transformer结构相似&#xff0c;但只用了decoder部分。 目录 1. GPT2. GPT23. GPT34. 知识补充4.1 下游任务实现方式4.2 sparse attention 1. GPT 预训练&#xff1a;无监督&#xff0c;根据前k个词预测下一个词的概率。微调&#xff1a; 有监督&a…

AI嵌入式人工智能开发 --- 【1】初始RKNPU

目录 一、NPU的由来 二、RKNPU介绍 三、RKNPU单核框架 3.1 AHB/AXI 接口 3.2 卷积神经网络加速单元&#xff08;CNA&#xff09; 3.3 数据处理单元&#xff08;Data Processing Unit&#xff0c;DPU&#xff09; 3.4 平面处理单元&#xff08;Planar Processing Unit&a…

#驱动开发

内核模块 字符设备驱动 中断、内核定时器 裸机开发和驱动开发的区别&#xff1f; 裸机开发 驱动开发&#xff08;基于内核&#xff09; 相同点 都能够控制硬件&#xff08;本质&#xff1a;操作寄存器&#xff09; 不同点 用C语言给对应的地址里面写值 按照一定的框架格式…

【DSP+FPGA】基于DSP+FPGA XC7K325T与TMS320C6678的通用信号处理平台

DSP FPGA 协同处理架构板载 1 个TMS320C6678 多核DSP处理节点板载 1 片 XC7K325T FPGA处理节点板载 1 个FMC 接口板载4路SFP光纤接口FPGA 与 DSP 之间采用高速Rapid IO互联 基于FPGA与DSP协同处理架构的通用高性能实时信号处理平台&#xff0c;该平台采用1片TI的KeyStone系列多…

CSS3 文本效果(text-shadow,box-shadow,white-space等)

一 text-shadow text-shadow 属性是 CSS3 中用于为文本添加阴影效果的工具。它可以增强文本的可读性和视觉吸引力&#xff0c;提供丰富的视觉效果 1 语法 text-shadow: offset-x offset-y blur-radius color;offset-x&#xff1a;阴影相对于文本的水平偏移量。可以是正值&am…

c/c++: function和procedure的区别

https://www.cs.nthu.edu.tw/~ychung/slides/CSC4180/Alfred%20V.%20Aho,%20Monica%20S.%20Lam,%20Ravi%20Sethi,%20Jeffrey%20D.%20Ullman-Compilers%20-%20Principles,%20Techniques,%20and%20Tools-Pearson_Addison%20Wesley%20(2006).pdf 函数与过程的区别&#xff0c;一个…

AI语音识别神器Openai Whisper对中文的支持如何?

文章目录 前言一、资料准备二、Whisper环境搭建第一步&#xff1a;安装whisper第二步&#xff1a;安装ffmpeg 三、Whisper测试总结其他相关 前言 语音识别一直以来都是人工智能领域中一个不容忽视的技术&#xff0c;随着大模型时代的到来&#xff0c;这项技术也发生了质的变化…