springboot实现excel数据导入导出

news2024/11/27 10:30:22

目录

1.一些关于excel的常识

2.使用Apache POI操作excel

3.使用easyexcel操作excel

4.结合mybatis-plus批量导入excel数据到数据库


1.一些关于excel的常识

首先关于excel的文件格式,分为xls和xlsx,分别对应03版本和07以后的版本。

03版本的excel最大行数限制为表格共有65536行,256列;而07版本的excel则无此限制。

关于excel中的单元格格式,一般为以下几类:

转为java代码时,可以初略地处理为:字符串、数值(普通数字、日期)、公式三类。由于不同的格式对应不同的java数据类型,所以读取时需要进行格式的转换。

2.使用Apache POI操作excel

Apache POI是创建和维护操作各种符合Office Open XML(OOXML)标准和微软的OLE 2复合文档格式(OLE2)的Java API。用它可以使用Java读取和创建,修改EXCEL文件。简单来说Apache POI 提供Java操作Excel进行读写的解决方案。

首先,我们需要知道的是,不同版本的excel对应POI不同版本下的依赖:

        <!--xls 03版-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.9</version>
        </dependency>
        <!--xlsx 07版-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.9</version>
        </dependency>

操作excel对象时,也对应不同的类:

//创建工作蒲(03版)
Workbook workbook_03 = new HSSFWorkbook();
//创建工作蒲(07版)
Workbook workbook_07 = new XSSFWorkbook();

(1)使用POI实现excel写

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.joda.time.DateTime;
import java.io.FileOutputStream;

public class ExcelWriter {

    private static final String path = "C:/Users/14125/Desktop/";

    public static void main(String[] args) throws Exception {
        //创建工作蒲(03版)
        Workbook workbook = new HSSFWorkbook();
        //Workbook workbook_07 = new XSSFWorkbook();
        //创建工作表
        Sheet sheet = workbook.createSheet();
        //创建行/列
        Row row1 = sheet.createRow(0);
        Cell cell_11 = row1.createCell(0);
        cell_11.setCellValue("测试一下");
        Cell cell_12 = row1.createCell(1);
        cell_12.setCellValue("测试两下");

        Row row2 = sheet.createRow(1);
        Cell cell_21 = row2.createCell(0);
        cell_21.setCellValue(new DateTime().toString("yyyy-MM-dd"));

        //生成一张表(IO流)
        FileOutputStream fileOutputStream = new FileOutputStream(path+"java_excel.xls");
        workbook.write(fileOutputStream);
        fileOutputStream.close();
    }
}

逻辑上很简单,创建工作蒲——》创建表——》创建行——》创建列,从而确定一个单元格——》往单元格写入值——》生成表,使用文件流输出——》关闭流。

 (1)使用POI实现excel读

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.joda.time.DateTime;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Date;

public class ExcelReader {

    private static final String path = "C:/Users/14125/Desktop/";

    public static void main(String[] args) throws IOException {
        //获取文件流
        FileInputStream fileInputStream = new FileInputStream(path+"test_read.xlsX");

        //获取工作蒲对象
        Workbook workbook = new XSSFWorkbook(fileInputStream);
        Sheet sheet = workbook.getSheetAt(1);
        //获取计算公式
        FormulaEvaluator formulaEvaluator = new XSSFFormulaEvaluator((XSSFWorkbook) workbook);

        Row rowTitle = sheet.getRow(0);
        if(rowTitle!=null) {
            //获取一行中数据数
            int cellCount = rowTitle.getPhysicalNumberOfCells();
            //获取标题行
            for (int i = 0; i < cellCount; i++) {
                Cell cell = rowTitle.getCell(i);
                if (cell != null) {
                    //都为String,直接读取
                    System.out.print(cell.getStringCellValue()+"| ");
                }
            }
            System.out.println();

            //获取行数
            int rowCount = sheet.getPhysicalNumberOfRows();

            //第二行非标题行开始遍历
            for (int i = 1; i < rowCount; i++) {
                Row rowData = sheet.getRow(i);
                if(rowData!=null){
                    for (int j = 0; j < cellCount; j++) {
                        Cell cell = rowData.getCell(j);
                        if (cell != null) {
                            //匹配单元格的数据类型
                            int cellType = cell.getCellType();
                            switch (cellType) {
                                //字符串。直接读取
                                case Cell.CELL_TYPE_STRING:
                                    String StringCellValue = cell.getStringCellValue();
                                    System.out.print(StringCellValue+"| ");
                                    break;
                                //数字类型,转换
                                case Cell.CELL_TYPE_NUMERIC:
                                    // 判断是否是日期
                                    if (DateUtil.isCellDateFormatted(cell)) {
                                        Date DateCellValue = cell.getDateCellValue();
                                        //转换日期格式
                                        System.out.print(new DateTime(DateCellValue)
                                                .toString("yyyy-MM-dd hh:mm:ss")+"| ");
                                    }else {
                                        //重新设置单元格的数据类型
                                        cell.setCellType(Cell.CELL_TYPE_STRING);
                                        //转换成字符串获取,防止数字过长
                                        System.out.print(cell.toString()+"| ");
                                    }
                                    break;
                                //boolean类型
                                case Cell.CELL_TYPE_BOOLEAN:
                                    boolean booleanCellValue = cell.getBooleanCellValue();
                                    System.out.print(booleanCellValue+"| ");
                                    break;
                                //公式类型
                                case Cell.CELL_TYPE_FORMULA:
//                                    //计算
//                                    CellValue evaluate = formulaEvaluator.evaluate(cell);
//                                    //获取计算后的值
//                                    String cellValuate = evaluate.formatAsString();
//                                    System.out.println(cellValuate+"| ");
                                    //重新设置单元格的数据类型
                                    double value = cell.getNumericCellValue();
                                    //转换成字符串获取,防止数字过长
                                    System.out.print(value+"| ");
                                    break;
                                //空白
                                case Cell.CELL_TYPE_BLANK:
                                    break;
                                // 匹配不能转换的错误
                                case Cell.CELL_TYPE_ERROR:
                                    break;
                            }
                        }
                    }

                }
                System.out.println();
            }
        }
        //关闭流
        fileInputStream.close();
    }
}

上述代码逻辑如上:创建文件流——》获取excel工作簿——》获取表头行——》 获取有内容单元格行数——》遍历所有行——》在每一行中获取有内容单元格列数——》遍历该行所有单元格——》判断单元格格式,并输出内容——》直到遍历所有单元格内容

上述代码中,核心代码为判断单元格格式,转为可处理的格式并进行输出:

        //匹配单元格的数据类型
        switch (cellType) {
            //字符串。直接读取
            case Cell.CELL_TYPE_STRING:
                String StringCellValue = cell.getStringCellValue();
                System.out.print(StringCellValue+"| ");
                break;
            //数字类型,转换
            case Cell.CELL_TYPE_NUMERIC:
                // 判断是否是日期
                if (DateUtil.isCellDateFormatted(cell)) {
                    Date DateCellValue = cell.getDateCellValue();
                    //转换日期格式
                    System.out.print(new DateTime(DateCellValue)
                            .toString("yyyy-MM-dd hh:mm:ss")+"| ");
                }else {
                    //重新设置单元格的数据类型
                    cell.setCellType(Cell.CELL_TYPE_STRING);
                    //转换成字符串获取,防止数字过长
                    System.out.print(cell.toString()+"| ");
                }
                break;
            //boolean类型
            case Cell.CELL_TYPE_BOOLEAN:
                boolean booleanCellValue = cell.getBooleanCellValue();
                System.out.print(booleanCellValue+"| ");
                break;
            //公式类型
            case Cell.CELL_TYPE_FORMULA:
                //计算
                CellValue evaluate = formulaEvaluator.evaluate(cell);
                //获取计算后的值
                String cellValuate = evaluate.formatAsString();
                System.out.println(cellValuate+"| ");
                //重新设置单元格的数据类型
                double value = cell.getNumericCellValue();
                //转换成字符串获取,防止数字过长
                System.out.print(value+"| ");
                break;
            //空白
            case Cell.CELL_TYPE_BLANK:
                break;
            // 匹配不能转换的错误
            case Cell.CELL_TYPE_ERROR:
                break;
        }
    }

实际开发中,我们可以将上述代码封装为一个工具类,传入一个excel文件流参数即可进行后续的调用。若在存储数据库,即在 Switch中转换格式后进行存储即可

3.使用easyexcel操作excel

EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。可以让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。

首先引入依赖,而easyexcel中已经包含了Apache poi相关依赖:

        <!--easy excel 已包含POI-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.0-beta2</version>
        </dependency>

(1)使用easyexcel写:

首先,创建实体类,@ExcelProperty(value="列名称",index = 列坐标);若是包含子标题的复杂标题,使用方法为:@ExcelProperty({"主标题", "字符串标题"})。

@ExcelIgnore 标签可以让实体类写入时,忽略该字段,不进行写入

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    @ExcelProperty(index = 0)
    private Integer id;

    @ExcelProperty(index = 1)
    private String age;

    @ExcelProperty(index = 2)
    private String name;

    @ExcelProperty(index = 3)
    private String gender;
}

easyexecl进行代码如下:

String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data());

4.结合mybatis-plus批量导入excel数据到数据库

 (2)使用easyexcel读:

首先,创建实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("student")
public class Student {

    @TableId(value = "id",type = IdType.AUTO)
    @ExcelProperty(index = 0)
    private Integer id;

    @ExcelProperty(index = 1)
    private String age;

    @ExcelProperty(index = 2)
    private String name;

    @ExcelProperty(index = 3)
    private String gender;
}

此处,为了简便mybatis-plus和easyexcel使用了同一个实体类,实际中应该为easyexcel封装一个dto,mybatis-plus单使用一个实体类,更为合理。

编写listener代码:

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import com.seven.excel.dao.StudentDao;
import com.seven.excel.entities.Student;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

@Slf4j
public class StudentDataListener extends AnalysisEventListener<Student> {

    /**
     * 每隔10条存储数据库,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 10;
    //缓存数据列表
    private List<Student> cachedDataList = new ArrayList<>(BATCH_COUNT);

    //不可以用spring管理,需用该方法实例化对象
    private StudentDao studentDao;
    public StudentDataListener(StudentDao studentDao) {
        this.studentDao = studentDao;
    }


    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(Student data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        Student student = new Student();
        cachedDataList.add(student);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            cachedDataList = new ArrayList<>(BATCH_COUNT);
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        if(cachedDataList.size()>0){
            log.info("{}条数据,开始存储数据库!", cachedDataList.size());
            studentDao.insertBatchSomeColumn(cachedDataList);
            log.info("存储数据库成功!");
        }
    }
}

此处,使用了mybatis-plus的批量插入,代码如下:

@Component
public class InsertBatchInjector extends DefaultSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo){
        List<AbstractMethod> methodList = super.getMethodList(mapperClass,tableInfo);
        // 过滤Insert语句中的字段
        methodList.add(new InsertBatchSomeColumn(t -> !"weekend".equals(t.getColumn())
                && !"position".equals(t.getColumn())
                && !"date".equals(t.getColumn())
        )); // 添加InsertBatchSomeColumn方法
        return methodList;
    }
}

其次,在一个新的包下面创建一个EasyBaseMapper继承baseMapper:

public interface EasyBaseMapper<T> extends BaseMapper<T> {
    /**
     * 批量插入 仅适用于mysql
     *
     * @param entityList 实体列表
     * @return 影响行数
     */
    Integer insertBatchSomeColumn(Collection<T> entityList);
}

然后,在另一个包下,创建自己的dao继承该 EasyBaseMapper:

@Mapper
public interface StudentDao extends EasyBaseMapper<Student> {
}

注意,二者代码不可放在同一个包下!

最后,编写控制类代码,读取excel文件,批量插入:

@RestController
public class StudentController {

    private static final String PATH = "C:/Users/14125/Desktop/";
    @Resource
    private StudentDao studentDao;

    @GetMapping("/read")
    public void read(@RequestParam("filename") String fileName){
        fileName = PATH + fileName;
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, Student.class, new StudentDataListener(studentDao)).sheet().doRead();
    }
}

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

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

相关文章

基于云边协同架构的五大应用场景革新

从概念到场景落地&#xff0c;边缘云加速革新&#xff0c;颠覆体验&#xff0c;拟造丰沛生态。边缘云的概念自明确以来已有四个多年头。 什么是边缘云&#xff1f; 边缘云&#xff0c;即把公共云的能力放在离数据发生端和消费端最近的地方&#xff0c;提升数据的处理效率&…

143.如何个性化推荐系统设计-3

143.1 算法介绍 协同过滤算法 协同过滤(Collaborative filtering, CF)算法是目前个性化推荐系统比较流行的算法之一。协同算法分为两个基本算法&#xff1a;基于用户的协同过滤&#xff08;UserCF&#xff09;和基于项目的协同过滤&#xff08;ItemCF&#xff09;。 基于属性…

m基于GA遗传优化的BP神经网络时间序列预测算法matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 将遗传算法&#xff08;GA&#xff09;与BP神经网络相结合&#xff0c;使用GA优化BP神经网络的主要参数。然后将影响输出响应值的多个特征因素作为GA-BP神经网络模型的输入神经元, 输出响应值作为…

[附源码]计算机毕业设计springboot绿色生鲜

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

nfs实现共享目录对于集群高可用风险,nfs客户端容易卡死

目录 背景说明 解决办法 执行步骤 数据移动 取消挂载 停nfs服务&#xff08;客户端&#xff09; 卸载nfs&#xff08;客户端&#xff09; 重启主机&#xff08;客户端&#xff09; 卸载nfs&#xff08;服务端&#xff09; 背景说明 nfs可以完成集群多个主机之间共享目…

感知算法工程师面试===目标检测===YOLO v3

引言 因为找工作的原因&#xff0c;面试中经常遇到目标检测的问题&#xff0c;在2022年的现在&#xff0c;大多是对单步目标检测算法YOLO家族系列展开一系列追问&#xff0c;比如Yolov3、Yolov4、Yolov5、Yolox算法。 另一部分就是Faster RCNN为代表的两阶段目标检测网络。 而…

[附源码]计算机毕业设计JAVA小区宠物管理系统

[附源码]计算机毕业设计JAVA小区宠物管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

blender UV基础

文章目录进入UV界面导出UV布局图拆分UV清除现有UV展开UV不同UV展开方式智能UV投射缝合边进入UV界面 1 选中一个物体&#xff0c;选择最上方UVEditing即可进入UV界面 2 注意进入UV界面后&#xff0c;舞台也会自动进入编辑模式 3 只有在编辑模式里才能操作UV 4 选择一个面来单独…

Linux学习-61-Linux系统服务管理

14 Linux系统服务管理 系统服务&#xff1a;服务是在后台运行的应用程序&#xff0c;并且可以提供一些本地系统或网络的功能。Linux 中常见的服务有那些&#xff0c;这些服务怎么分类&#xff0c;服务如何启动&#xff0c;服务如何自启动&#xff0c;服务如何查看&#xff1f;…

Codeforces Round #725 (Div. 3) F. Interesting Function

翻译&#xff1a; 给出两个整数&#x1d459;和&#x1d45f;&#xff0c;其中&#x1d459;<&#x1d45f;。我们将在&#x1d459;上加1&#xff0c;直到结果等于&#x1d45f;。因此&#xff0c;执行的添加恰好是&#x1d45f;−&#x1d459;。对于每一个这样的加法&am…

pytest文档84 - 把收集的 yaml 文件转成pytest 模块和用例

前言 前面实现了一个基础的读取yaml文件内容&#xff0c;当成用例去执行。虽然入门简单&#xff0c;但需要扩展功能&#xff0c;比如在 yaml 用例实现参数化&#xff0c;就不好扩展了。 因为它并不是一个真正的pytest的模块和用例&#xff0c;无法被钩子函数探测到。所以这篇会…

CV攻城狮入门VIT(vision transformer)之旅——VIT原理详解篇

&#x1f34a;作者简介&#xff1a;秃头小苏&#xff0c;致力于用最通俗的语言描述问题 &#x1f34a;往期回顾&#xff1a;CV攻城狮入门VIT(vision transformer)之旅——近年超火的Transformer你再不了解就晚了&#xff01; &#x1f34a;近期目标&#xff1a;写好专栏的每一篇…

Linux centos7.6 安装elasticsearch8.x (es8) 教程

系列-Linux centos7.6 安装elasticsearch8.x (es8) 教程 Linux centos7.6 安装elasticsearch8.x (es8) 教程_言之有李LAX的博客-CSDN博客 系列-linux安装elasticsearch-head &#xff08;es可视化界面&#xff09; linux安装elasticsearch-head &#xff08;es可视化界面&am…

隆重推出 Incredibuild 10

变更可能来的很快&#xff0c;也可能来的很慢。但有时候&#xff0c;它真的值得我们等待。您并非每天都能目睹一个很棒的平台进一步发展成为一个不可思议&#xff08;Incredible&#xff09;的平台。今天&#xff0c;我们将正式发布最新版本——Incredibuild 10&#xff01;经过…

SIMetrix导入MOS管参数的另一种方法

问题的提出 在采用SIMetrix 8.3软件进行E类放大器的仿真过程中&#xff0c;用到了NEXPERIA公司的NMOS管器件PMH550UNE, 但在SIMetrix 8.3的库中没有该器件&#xff0c;因此需要导入第三方库文件. 通常的办法是从生产该器件的公司网站上下载器件库文件&#xff0c;导入到SIMet…

《QDebug 2022年11月》

一、Qt Widgets 问题交流 二、Qt Quick 问题交流 1.QtQuick.Dialogs 1.x 中的 MessageDialog 触发两次 accepted 、rejected [QTBUG-94126] If inherit QApplication, the MessageDialog accepted signal is emitted twice. - Qt Bug Tracker 当使用 QApplication 而不是 Q…

React 中ref 的使用(类组件和函数组件)以及forwardRef 与 useImperativeHandle 详解

前言 在一个父组件中&#xff0c;我们想要获取到其中的节点元素或者子组件实例&#xff0c;从而直接调用其上面的方法。Class 类组件和函数组件是两种不同的写法。 1. Class 组件中使用ref 在 React 的 Class 组件中&#xff0c;我们通过 createRef 创建 ref class Parent …

传统制造企业进行数字化转型,是翻身还是翻船?

数实融合正在从可选项&#xff0c;变成每个行业都要面对的必选项&#xff0c;制造企业也从野蛮生长逐渐步入有序的数字化世界。 出品|产业家 2022年&#xff0c;疫情及经济环境全面淬炼了各行各业&#xff0c;大多数能有效应用数字化持续经营的企业成为幸存者&#xff0c;数字…

信号与进程间通信

目录结束进程结束后台进程结束前台进程信号基本概念接收信号发送信号代码演示接收信号函数&#xff08;signal&#xff09;SIG_IGNSIG_DFL自定义函数发送信号&#xff08;kill&#xff09;接收信号解决僵尸进程结束进程 结束后台进程 终端1&#xff1a;./main killed 终端2&a…

Linux系统编程(五)——Linux下的多线程

目录 0x01 线程概述 一、线程和进程的区别 二、线程和进程的虚拟地址空间 三、线程之间共享的非共享资源 四、NPTL 0x02 创建线程 0x03 终止线程 0x04 连接已终止的进程 0x05 线程的分离 0x06 线程取消 0x07 线程属性 0x08 线程同步 一、互斥锁 二、死锁 三、如何…