Apache POI Excel 处理最佳实践

news2024/10/7 10:31:44

1、需求背景

问题的背景是在需求设计的时候 ,我们在业务专家的配合下设计了一些表,但是为了方便的和他们讨论我们把表结构的描述通过Excel文件的方式记录了下来,然后我们需要根据excel文件中的内容生成对应的DDL。今天就给大家分享一下我们的解决方案

2、编码实现

2.1、思路设计

我们当时定义的格式是 第一页存放目录,后续的每个sheet页存放一张表的表结构定义,具体的格式如下图所示

每个sheet页内容如下:

好了,Excel文件的格式就是这样的,我们下面来读取这个文件,然后根据文件内容生成建表的语句。

具体的实现思路如下

1、读取首页(目录页)的内容,将每行数据封装成一个对象,对象的属性有 表名、序号、表中文名,将每行数据生成的对象存入到一个Map中,表名作为 Key,对象作为 Value。

2、遍历第一步中得到的Map,根据表名读取对应的 Sheet 页,从第二行开始遍历行,按照指定的列读取相关的字段信息,拼接成SQL。将每个 Sheet 页对应的SQL 缓存在Map中。表名作为Key,SQL作为 Value 

3、遍历第二步中获取到的Map 将Value 按行写入到文件中

 2.2、编码落地

2.2.1、引入依赖

首先我们需要用到的依赖如下

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.3</version>
        </dependency>

2.2.2、定义数据表对象

public class DataCatalogue {

    //页码
    private Integer id;
    //表名
    private String tableName;
    //表中文名
    private String tableMSg;

    //TODO 
    // setter  getter  ......
}

上述对象一共三个属性,其中 序号合表名都可以用来读取后续的表信息,表中文名后续会拼接到SQL中 

2.2.3、读取 Excel 文件 

首先我们使用 FileInputStream 读取 Excel 文件,然后包装成 POI 工具包的 XSSFWorkbook 对象,该对象就是整个Excel工作簿的抽象,里面包含了整个工作簿的内容,相关代码如下

    /**
    * @Description  根据文件路径加载Excel文件
    * @Param [filePath]
    * @return org.apache.poi.xssf.usermodel.XSSFWorkbook
    * @Date 2024/6/29 下午 21:00
    * @Author wcan
    * @Version 1.0
    */
    public static XSSFWorkbook getWorkBook(String filePath) throws IOException {
        FileInputStream fileInputStream = new FileInputStream(new File(filePath));
        return new XSSFWorkbook(fileInputStream);
    }

 读取表清单内容

/**
     * @return java.util.HashMap<java.lang.Integer, org.wcan.file.pojo.DataCatalogue>
     * @Description 读取目(表清单)录内容
     * @Param [workbook]
     * @Date 2024/6/29 下午 20:59
     * @Author wcan
     * @Version 1.0
     */
    public static HashMap<Integer, DataCatalogue> readTableListFromExcel(XSSFWorkbook workbook) throws IOException {
        //获取目录
        XSSFSheet catalogueSheet = workbook.getSheetAt(0);
        //将目录内容放到Map中
        HashMap<Integer, DataCatalogue> catalogueMap = new HashMap<>();
        catalogueSheet.forEach(row -> {
            int rowNum = row.getRowNum();
            if (rowNum > 0) {
                DataCatalogue dataCatalogue = new DataCatalogue();
                Iterator<Cell> iterator = row.iterator();
                while (iterator.hasNext()) {
                    Cell cell = iterator.next();
                    int columnIndex = cell.getColumnIndex();
                    String cellValue = cell.getStringCellValue();
                    //序号
                    if (columnIndex == 0) {
                        dataCatalogue.setId(Integer.valueOf(cellValue));
                    }
                    //表名
                    if (columnIndex == 1) {
                        dataCatalogue.setTableName(cellValue);
                    }
                    //中文名
                    if (columnIndex == 2) {
                        dataCatalogue.setTableMSg(cellValue);
                    }
                }
                catalogueMap.put(rowNum, dataCatalogue);
            }
        });
        return catalogueMap;
    }

生成SQL语句,需要注意的是 这个我们为了兼容MySQL中的关键词,对所有的字段名和表名都加上了反引号

 /**
     * @return java.util.HashMap<java.lang.String, java.lang.String>
     * @Description 拼接SQL语句
     * @Param [catalogueMap, workbook]
     * @Date 2024/6/29 下午 20:58
     * @Author wcan
     * @Version 1.0
     */
    public static HashMap<String, String> ddlSQLBuilder(HashMap<Integer, DataCatalogue> catalogueMap, XSSFWorkbook workbook) {
        HashMap<String, String> dataBeanHashMap = new HashMap<>();
        Iterator<Map.Entry<Integer, DataCatalogue>> iterator = catalogueMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, DataCatalogue> next = iterator.next();
            DataCatalogue dataCatalogue = next.getValue();
            //表名
            String tableName = dataCatalogue.getTableName();
            String tableMSg = dataCatalogue.getTableMSg();
            XSSFSheet sheet = workbook.getSheet(tableName);
            StringBuilder sqlBuilder = new StringBuilder();
            String sql = "CREATE TABLE IF NOT EXISTS  ";
            sqlBuilder.append(sql);
            StringBuilder primaryKeyBuilder = new StringBuilder();
            sqlBuilder.append("`").append(tableName).append("` (");
            sheet.forEach(row -> {
                int rowNum = row.getRowNum();
                if (rowNum > 0) {
                    Cell columnCell = row.getCell(0);
                    String columnName = columnCell.getStringCellValue();
                    if (null != columnName && columnName.length() > 0) ;
                    sqlBuilder.append("`").append(columnName.trim()).append("`").append(" ");

                    Cell dataTypeCell = row.getCell(2);
                    String dataType = dataTypeCell.getStringCellValue();
                    if (null != dataType && dataType.length() > 0) ;
                    sqlBuilder.append(dataType.trim());

                    Cell columnLengthCell = row.getCell(3);
                    if (null != columnLengthCell) {
                        String columnLength = columnLengthCell.getStringCellValue();
                        if (null != columnLength && columnLength.length() > 0) {
                            sqlBuilder.append("(").append(columnLength.trim()).append(")").append(" ");
                        }
                    }

                    Cell isNotNullCell = row.getCell(6);
                    if (null != isNotNullCell) {
                        String cellValue = isNotNullCell.getStringCellValue();
                        if ("YES".equals(cellValue))
                            sqlBuilder.append(" NOT NULL");
                    }

                    Cell columnMsgCell = row.getCell(8);
                    if (null != columnMsgCell) {
                        String cellValue = columnMsgCell.getStringCellValue();
                        if (null != cellValue && cellValue.length() > 0) {
                            sqlBuilder.append(" COMMENT ").append("'").append(cellValue.trim()).append("' ,");
                        }

                    }
                    Cell primaryKeyCell = row.getCell(5);
                    if (primaryKeyCell != null) {
                        String cellValue = primaryKeyCell.getStringCellValue();
                        if ("YES".equals(cellValue))
                            primaryKeyBuilder.append("PRIMARY KEY (`").append(columnName.trim()).append("`)");
                    }
                }
                sqlBuilder.append(" \n ");

            });
            sqlBuilder.append(primaryKeyBuilder.toString()).append(" \n )").append(" ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='")
                    .append(tableMSg).append("';");
            dataBeanHashMap.put(tableName, sqlBuilder.toString());
        }
        return dataBeanHashMap;
    }

最后将SQL语句按行写入文件中

 /**
    * @Description 将生成的SQL语句写入文件
    * @Param [ddlMsgMap, filePath, fileName]
    * @return void
    * @Date 2024/6/29 下午 21:01
    * @Author wcan
    * @Version 1.0
    */
    public static void builderDDLFile(HashMap<String, String> ddlMsgMap, String filePath, String fileName) throws IOException {
        BufferedWriter writer = new BufferedWriter(new FileWriter(filePath + File.separator + fileName));
        ddlMsgMap.forEach((key, value) -> {
            try {
                writer.write(value);
                writer.newLine();//换行
                writer.newLine();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
        writer.close();
    }

 2.2.4、功能测试

我们编写测试方法

public static void main(String[] args) throws IOException {
        String filePath = "D:\\project\\testfile\\数据字典.xlsx";
        XSSFWorkbook workBook = getWorkBook(filePath);
        HashMap<Integer, DataCatalogue> dataCatalogueMap = readTableListFromExcel(workBook);
        HashMap<String, String> ddlSQLMap = ddlSQLBuilder(dataCatalogueMap, workBook);
        builderDDLFile(ddlSQLMap, "D:\\project\\testfile\\", "ddl2.sql");
    }

我们去指定的位置查看文件,内容如下

至此功能实现成功了,大家也可以试试。 

3、POI 最佳实践

上面我们使用POI工具实现了从Excel文件中获取表字段信息,生成SQL语句的功能,从上述案例中我们可以看出获取页和获取单元格的值我都写了不同的方式读取

3.1、获取Sheet页

上述案例中在获取Sheet页的时候我用了2种不同的方式,比如获取目录页的时候我是通过页码获取的,而后面我在处理表字段的时候 是通过Sheet页的名字获取的。对于这两种方式大家 可以灵活的选择。相关api方法如下:

            //获取总页数
            int numberOfSheets = workbook.getNumberOfSheets();
            //根据页码获取sheet名
            String sheetName = workbook.getSheetName(1);
            //根据sheet名称 获取sheet 页
            XSSFSheet sheet1 = workbook.getSheet(sheetName);

我们不难看出通过页码去读取Sheet页肯定效率要高一些,至于通过Sheet名获取是怎么实现的大家估计心里也很清楚,肯定就是迭代整个工作簿,一次判断每个Sheet页的名字。具体实现我们可以参见相关源码

    /**
     * Get sheet with the given name (case insensitive match)
     *
     * @param name of the sheet
     * @return XSSFSheet with the name provided or {@code null} if it does not exist
     */
    @Override
    public XSSFSheet getSheet(String name) {
        for (XSSFSheet sheet : sheets) {
            if (name.equalsIgnoreCase(sheet.getSheetName())) {
                return sheet;
            }
        }
        return null;
    }

上述代码片段来自 org.apache.poi.xssf.usermodel.XSSFWorkbook文件第1167行。

3.2、获取单元格的值

同样的我们在获取单元格的值的时候也使用了不同的方式

1、我们在读取首页(表目录)的时候 循环读取当前Sheet页的每一行内容,然后通过while循环去读取每一行的每一列的值。

2、我们既然都知道了每一列都存放的是什么内容,那我们完全可以指定每一列的序号去读取,所以在第二次获取具体的表字段信息的时候 我们是循环当前Sheet页的每一行,对于每一行数据我们直接通过列的序号获取对应的值。

关于这两种方式都可以获取到我们想要的内容,大家可以根据自己实际的业务场景选择。

3.3、关于数据类型

在上述案例的代码中大家可以发现我每次读取的时候都是使用的字符串类型的变量接收单元格里的数据,你如果运行上述代码很有可能会报错,报错的原因就是单元格类型并不是文本类型,所以你使用 getStringCellValue 去读数据的时候就会抛出异常。所以在使用上述代码的时候一定要记得将Excel文件中全部设置成文本格式的数据。

关于数据类型POI 也提供了一组解决方案,这里给大家总结了一下相关的api方法

   //获取某个单元格的值对象
   Cell dataTypeCell = row.getCell(2);
   //获取单元格的列
   int columnIndex = columnCell.getColumnIndex();
   //获取行
   Row row = dataTypeCell.getRow();
   //获取单元格类型 返回一个枚举
   CellType cellType = columnCell.getCellType();
   //按数字类型获取值 返回一个double类型的数字
   double numericCellValue = dataTypeCell.getNumericCellValue();
   //按字符串的类型湖区值
   String cellValue = dataTypeCell.getStringCellValue();
   //读取布尔值
   boolean booleanCellValue = dataTypeCell.getBooleanCellValue();
   //读取日期格式的数据
   Date dateCellValue = dataTypeCell.getDateCellValue();

因为本次案例中我是生成SQL语句,最终肯定是要当成文本内容写入到文件中的,所以我这里一律按照字符串类型处理,那么如果你需要解析不同类型的数据就可以使用上述提供的方法去解析

关于 POI 支持的数据类型,我们可以查看 org.apache.poi.ss.usermodel.CellType  这个枚举类

package org.apache.poi.ss.usermodel;

import org.apache.poi.ss.formula.FormulaType;
import org.apache.poi.util.Internal;

/**
 * @since POI 3.15 beta 3
 */
public enum CellType {
    /**
     * Unknown type, used to represent a state prior to initialization or the
     * lack of a concrete type.
     * For internal use only.
     */
    @Internal(since="POI 3.15 beta 3")
    _NONE(-1),

    /**
     * Numeric cell type (whole numbers, fractional numbers, dates)
     */
    NUMERIC(0),
    
    /** String (text) cell type */
    STRING(1),
    
    /**
     * Formula cell type
     * @see FormulaType
     */
    FORMULA(2),
    
    /**
     * Blank cell type
     */
    BLANK(3),
    
    /**
     * Boolean cell type
     */
    BOOLEAN(4),
    
    /**
     * Error cell type
     * @see FormulaError
     */
    ERROR(5);

    /**
     * @since POI 3.15 beta 3
     * @deprecated POI 3.15 beta 3
     */
    @Deprecated
    private final int code;

    private CellType(int code) {
        this.code = code;
    }

    /**
     * @since POI 3.15 beta 3.
     * @deprecated POI 3.15 beta 3. Used to transition code from <code>int</code>s to <code>CellType</code>s.
     */
    @Deprecated
    public static CellType forInt(int code) {
        for (CellType type : values()) {
            if (type.code == code) {
                return type;
            }
        }
        throw new IllegalArgumentException("Invalid CellType code: " + code);
    }
    
    /**
     * @since POI 3.15 beta 3
     * @deprecated POI 3.15 beta 3
     */
    @Deprecated
    public int getCode() {
        return code;
    }
    
}

3.4、总结

我们在读取某个单元格数据的时候原则上先要判断该单元格数据值类型,再根据类型使用对应的方法去读取这样就可以避免发生类型不匹配的异常,例如下面这段代码

     Cell dataTypeCell = row.getCell(2);
     CellType cellType = dataTypeCell.getCellType();

我们在实际项目中这么写的话可能会存在风险,假设有人上传了一个Excel文件,数字类型的值没有设置成文本内容,那么第二行代码就会发生异常,所以我们在读数据的时候需要先判断类型

    Object dataType = null;
    if (cellType == CellType.STRING) {
        dataType = dataTypeCell.getStringCellValue();
    }
    if (cellType == CellType.NUMERIC) {
        dataType = dataTypeCell.getNumericCellValue();
    }
    // .......

同样的我们读取出来的dataTypeCell  也需要事先判空,避免发生空指针异常。

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

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

相关文章

python-求出 e 的值

[题目描述] 利用公式 e11/1!1/2!1/3!⋯1/&#x1d45b;!&#xff0c;求 e 的值&#xff0c;要求保留小数点后 10 位。输入&#xff1a; 输入只有一行&#xff0c;该行包含一个整数 n&#xff0c;表示计算 e 时累加到1/n!。输出&#xff1a; 输出只有一行&#xff0c;该行包含计…

解锁数据资产的无限潜能:深入探索创新的数据分析技术,挖掘其在实际应用场景中的广阔价值,助力企业发掘数据背后的深层信息,实现业务的持续增长与创新

目录 一、引言 二、创新数据分析技术的发展 1、大数据分析技术 2、人工智能与机器学习 3、可视化分析技术 三、创新数据分析技术在实际应用场景中的价值 1、市场洞察与竞争分析 2、客户细分与个性化营销 3、业务流程优化与风险管理 4、产品创新与研发 四、案例分析 …

单片机的学习(15)--LCD1602

LCD1602 14.1LCD1602的基础知识1.LCD1602介绍2.引脚及应用电路3.内部结构框图4.时序结构5.LCD1602指令集6.字符值7.LCD1602操作流程 14.2LCD1602功能函数代码1.显示一个字符&#xff08;1&#xff09;工程目录&#xff08;2&#xff09;main.c函数&#xff08;3&#xff09;LCD…

mysql8.0-学习

文章目录 mysql8.0基础知识-学习安装mysql_8.0登录mysql8.0的体系结构与管理体系结构图连接mysqlmysql8.0的 “新姿势” mysql的日常管理用户安全权限练习查看用户的权限回收:revoke角色 mysql的多种连接方式socket显示系统中当前运行的所有线程 tcp/ip客户端工具基于SSL的安全…

error: Sandbox: rsync.samba in Xcode project

在Targets 的 Build Settings 搜索&#xff1a;User script sandboxing 设置为NO

什么是TOGAF?TOGAF应用场景有哪些?TOGAF优缺点

一、什么是TOGAF&#xff1f; TOGAF&#xff0c;全称The Open Group Architecture Framework&#xff0c;即开放组体系结构框架&#xff0c;是由国际开放标准组织The Open Group制定的一套企业架构&#xff08;Enterprise Architecture, EA&#xff09;框架。 The TOGAF Libra…

Mysql基本知识点

1.数据库的基本操作 显示当前的数据库 show databases;创建一个数据库 直接创建数据库 create database 数据库名字;如果系统没有 test2 的数据库&#xff0c;则创建一个名叫 test2 的数据库&#xff0c;如果有则不创建 create database if not exists test2;如果系统没有 db…

在navicat对mysql声明无符号字段

1.无符号设置 在 MySQL 中&#xff0c;我们可以使用 UNSIGNED 属性来设置列的无符号属性&#xff0c;这意味着该列只能存储非负整数值。对于一些需要存储正整数的列&#xff0c;比如年龄、数量等&#xff0c;使用 UNSIGNED 属性可以提高数据存储和查询的效率&#xff0c;并且能…

【启明智显分享】乐鑫ESP32-S3R8方案2.8寸串口屏:高性能低功耗,WIFI/蓝牙无线通信

近年来HMI已经成为大量应用聚焦的主题&#xff0c;在消费类产品通过创新的HMI设计带来增强的连接性和更加身临其境的用户体验之际&#xff0c;工业产品却仍旧在采用物理接口。这些物理接口通常依赖小型显示器或是简单的LED&#xff0c;通过简单的机电开关或按钮来实现HMI交互。…

【云原生】Prometheus 使用详解

目录 一、前言 二、服务监控概述 2.1 什么是微服务监控 2.2 微服务监控指标 2.3 微服务监控工具 三、Prometheus概述 3.1 Prometheus是什么 3.2 Prometheus 特点 3.3 Prometheus 架构图 3.3.1 Prometheus核心组件 3.3.2 Prometheus 工作流程 3.4 Prometheus 应用场景…

代码托管服务:GitHub、GitLab、Gitee

目录 引言GitHub&#xff1a;全球最大的代码托管平台概述功能特点适用场景 GitLab&#xff1a;一体化的开发平台概述功能特点适用场景 Gitee&#xff08;码云&#xff09;&#xff1a;中国本土化的代码托管服务概述功能特点适用场景 功能对比结论 引言 在现代软件开发中&#…

江协科技51单片机学习- p21 LED点阵屏(8*8)

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

15.数据库简介+MySQl使用+SQL语句

文章目录 数据库简述一.数据库简介DB1.定义:2.DBMS数据库管理系统3.数据库分类 二.MySQL的安装1.安装步骤2.MySQL数据库图形管理工具3.mysql程序常用命令4.MySQL字符集及字符序5.Navicat快捷键操作 三.MySQL数据库基本操作 .........................................表管理一.…

Jetson系列机载电脑创建热点模式配置方法

Jetson nano为例—— 创建热点模式配置方法 1.1、新建一个 WiFi 在屏幕右上角找到网络图标&#xff0c;点击后选择“Edit Connections”选项&#xff0c;进入选择网络连接页面&#xff0c;然后点击左下角加号&#xff0c;新建一个连接&#xff0c;类型选择 WiFi 后点击 “cre…

【面试系列】数据工程师高频面试题及详细解答

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题. ⭐️ AIGC时代的创新与未来&#xff1a;详细讲解AIGC的概念、核心技术、…

Spark学习3.0

目录 10.3.4 Spark运行原理 1.设计背景 2.RDD概念 3.RDD特性 4.RDD之间的依赖关系 窄依赖和宽依赖 5.Stage的划分 Stage的类型包括两种&#xff1a;ShuffleMapStage和ResultStage 6.RDD运行过程 10.3.4 Spark运行原理 1.设计背景 许多 迭代式算法&#xff08;比如机器学习、图…

Hadoop-03-Hadoop集群 免密登录 超详细 3节点公网云 分发脚本 踩坑笔记 SSH免密 服务互通 集群搭建 开启ROOT

章节内容 上一节完成&#xff1a; HDFS集群XML的配置MapReduce集群XML的配置Yarn集群XML的配置统一权限DNS统一配置 背景介绍 这里是三台公网云服务器&#xff0c;每台 2C4G&#xff0c;搭建一个Hadoop的学习环境&#xff0c;供我学习。 之前已经在 VM 虚拟机上搭建过一次&…

【python系统学习笔记day1】软件安装与使用

一、正确学习方法 坚持&#xff01; 第一阶段&#xff1a;按照老师的步骤做&#xff0c;不要跳步 第二阶段&#xff1a;做出个人创新与尝试 第三阶段&#xff1a;建立自己的体系 二、环境的安装 第一步&#xff1a;任意浏览器网址栏输入 python.org 第二步&#xff1a;点…

一文弄懂逻辑回归算法

1. 引言 今天我们将深入探讨另一种基本的机器学习算法&#xff1a;逻辑回归。在前两篇文章中&#xff0c;我们使用线性回归和梯度下降法帮助我们的朋友马克确定了他 2400 平方英尺房子的理想售价。 最近马克再次向我们求助。他住在一个高档社区&#xff0c;他认为低于一定面积…

探索人工智能和LLM对未来就业的影响

近年来&#xff0c;人工智能&#xff08;AI&#xff09;迅猛发展&#xff0c;引发了人们的兴奋&#xff0c;同时也引发了人们对就业未来的担忧。大型语言模型&#xff08;LLM&#xff09;就是最新的例子。这些强大的人工智能子集经过大量文本数据的训练&#xff0c;以理解和生成…