poi导入数据工具类,直接复制使用,有详细注释

news2025/8/3 13:33:42

poi导入工具类,直接复制使用,有详细的注释

  • 前言
  • 一、引入依赖
  • 二、封装的工具类以及注解类直接copy使用
    • 首先是工具类无需做操作
    • 然后是封装的两个注解类,也是直接复制使用
  • 测试工具类功能
    • 测试实体类
    • Controller层调用
  • 执行结果
    • 如果ifNull 设置为true执行结果
  • 仰天大笑出门去,我辈岂是蓬蒿人


前言

本文基于poi、注解类加反射实现自定义文件表头的导入功能、只需要将表头名写入到实体类的注解类上,就可以实现自定义文件的万能导入功能工具类;

这是导入的文件数据格式,只读取并导入红色区域的内容
在这里插入图片描述


一、引入依赖

使用到的依赖

    <!-- excel工具 -->
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi</artifactId>
      <version>3.13</version>
    </dependency>
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-ooxml</artifactId>
      <version>3.13</version>
    </dependency>
    <!--工具类-->
    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.7.3</version>
    </dependency>
    <!--实体类get set 构造方法注解-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.12</version>
    </dependency>

	<!--常用工具类 -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
    </dependency>

    <!-- io常用工具类 -->
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.11.0</version>
    </dependency>

    <!-- 文件上传工具类 -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.4</version>
    </dependency>

二、封装的工具类以及注解类直接copy使用

一共需要三个java文件,工具类和两个注解类

首先是工具类无需做操作

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.study.test.util.note.Description;
import com.study.test.util.note.Excel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
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.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 注释: excel工具类
 *
 * @author yangyongzhuo 2022/11/17 19:59
 */
@Slf4j
public class ExcelUtils {

    private final static String EXCEL2003 = "xls";
    private final static String EXCEL2007 = "xlsx";

    /**
     * 通过clsss,读取excel里面的数据,只要表头与Excel里面的notes一致就可以,不要关注顺序
     *
     * @param file  文件
     * @param clsss vo
     * @return vo集合
     * @return java.util.List<T>
     * @author yangyongzhuo 2022/11/17 19:59
     */
    public static <T> List<T> readExcel(MultipartFile file, Class<T> clsss) {
        //开始执行时间
        long start = System.currentTimeMillis();

        Workbook workbook = null;
        //返回数据对象
        List<T> dataList = null;

        //先判断文件命名是否正确,再判断文件是哪种类型
        String fileName = file.getName();
        if (!fileName.matches("^.+\\.(?i)(xls)$") && !fileName.matches("^.+\\.(?i)(xlsx)$")) {
            log.error("上传文件格式不正确");
            throw new RuntimeException("Excel命名格式不正确!");
        }
        try {
            InputStream is = file.getInputStream();
            if (fileName.endsWith(EXCEL2007)) {
                workbook = new XSSFWorkbook(is);
            }
            if (fileName.endsWith(EXCEL2003)) {
                workbook = new HSSFWorkbook(is);
            }
            if (ObjectUtil.isEmpty(workbook)) {
                throw new RuntimeException("Excel格式不正确,未获取工作空间!");
            }

            dataList = new ArrayList<>();
            //通过反射获取注释类上的数据下标值
            Description annotation = clsss.getAnnotation(Description.class);
            //数据位置因为poi读取下标是从0开始,所以要减1
            int dataIndex = annotation.dataIndex() - 1;
            //是否忽略空行
            boolean ifNull = annotation.ifNull();

            // 类映射 注解,拿到文件字段名称
            Map<String, List<Field>> classMap = new HashMap<>();
            List<Field> fields = Stream.of(clsss.getDeclaredFields()).collect(Collectors.toList());
            fields.forEach(field -> {
                //
                Excel excel = field.getAnnotation(Excel.class);
                if (ObjectUtil.isEmpty(excel)) {
                    return;
                }
                String notes = excel.notes();
                if (StrUtil.isEmpty(notes)) {
                    return;
                }
                if (!classMap.containsKey(notes)) {
                    classMap.put(notes, new ArrayList<>());
                }
                field.setAccessible(true);
                classMap.get(notes).add(field);
            });
            // 获取字段对应的列号
            Map<Integer, List<Field>> reflectionMap = new HashMap<>(16);
            // 默认读取第一个sheet
            Sheet sheet = workbook.getSheetAt(0);
            //
            boolean firstRow = true;
            for (int i = sheet.getFirstRowNum(); i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                // 表头区域获取字段对应的列
                if (i < dataIndex) {
                    for (int j = row.getFirstCellNum(); j <= row.getLastCellNum(); j++) {
                        Cell cell = row.getCell(j);
                        String cellValue = getCellValue(cell);
                        if (classMap.containsKey(cellValue)) {
                            reflectionMap.put(j, classMap.get(cellValue));
                        }
                    }
                    firstRow = false;
                } else {
                    // 忽略空白行
                    if (row == null) {
                        continue;
                    }
                    try {
                        T t = clsss.newInstance();
                        // 判断是否为空白行
                        boolean allBlank = true;
                        for (int j = row.getFirstCellNum(); j <= row.getLastCellNum(); j++) {
                            if (reflectionMap.containsKey(j)) {
                                Cell cell = row.getCell(j);
                                String cellValue = getCellValue(cell);
                                if (StringUtils.isNotBlank(cellValue)) {
                                    allBlank = false;
                                }
                                List<Field> fieldList = reflectionMap.get(j);
                                fieldList.forEach(x -> {
                                    try {
                                        handleField(t, cellValue, x);
                                    } catch (Exception e) {
                                        log.error(String.format("reflect field:%s value:%s exception!", x.getName(),
                                                cellValue), e);
                                    }
                                });
                            }
                        }
                        if (!allBlank) {
                            dataList.add(t);
                        } else {
                            //if is null return this import code block
                            if (ifNull) {
                                return dataList;
                            }
                            log.warn(String.format("row:%s is blank ignore!", i));
                        }
                    } catch (Exception e) {
                        log.error(String.format("parse row:%s exception!", i), e);
                    }
                }
            }
        } catch (Exception e) {
            log.error(String.format("parse excel exception!"), e);
        } finally {
            if (workbook != null) {
                try {
                    workbook.close();
                } catch (Exception e) {
                    log.error(String.format("parse excel exception!"), e);
                }
            }
        }
        long end = System.currentTimeMillis();
        log.info("read excel cost {}s", (end - start) / 1000);
        return dataList;
    }

    /**
     * 注释: 获取数据原始类型
     *
     * @param t
     * @param value
     * @param field
     * @return void
     * @author yangyongzhuo 2022/11/25 13:25
     */
    private static <T> void handleField(T t, String value, Field field) throws Exception {
        Class<?> type = field.getType();
        if (type == null || type == void.class || StringUtils.isBlank(value)) {
            return;
        }
        if (type == Object.class) {
            field.set(t, value);
            // 数字类型
        } else if (type.getSuperclass() == null || type.getSuperclass() == Number.class) {
            if (type == int.class || type == Integer.class) {
                field.set(t, NumberUtils.toInt(value));
            } else if (type == long.class || type == Long.class) {
                field.set(t, NumberUtils.toLong(value));
            } else if (type == byte.class || type == Byte.class) {
                field.set(t, NumberUtils.toByte(value));
            } else if (type == short.class || type == Short.class) {
                field.set(t, NumberUtils.toShort(value));
            } else if (type == double.class || type == Double.class) {
                field.set(t, NumberUtils.toDouble(value));
            } else if (type == float.class || type == Float.class) {
                field.set(t, NumberUtils.toFloat(value));
            } else if (type == char.class || type == Character.class) {
                field.set(t, CharUtils.toChar(value));
            } else if (type == boolean.class) {
                field.set(t, BooleanUtils.toBoolean(value));
            } else if (type == BigDecimal.class) {
                field.set(t, new BigDecimal(value));
            }
        } else if (type == Boolean.class) {
            field.set(t, BooleanUtils.toBoolean(value));
        } else if (type == Date.class) {
            field.set(t, value);
        } else if (type == String.class) {
            field.set(t, value);
        } else {
            Constructor<?> constructor = type.getConstructor(String.class);
            field.set(t, constructor.newInstance(value));
        }
    }

    /**
     * 注释:  获取数据类型
     *
     * @param cell
     * @return java.lang.String
     * @author yangyongzhuo 2022/11/25 13:26
     */
    private static String getCellValue(Cell cell) {
        if (cell == null) {
            return "";
        }

        int cellType = cell.getCellType();
        if (cellType == Cell.CELL_TYPE_FORMULA) { // 表达式类型
            cellType = cell.getCachedFormulaResultType();
        }

        if (cellType == Cell.CELL_TYPE_NUMERIC) {
            if (HSSFDateUtil.isCellDateFormatted(cell)) {
                Date date = HSSFDateUtil.getJavaDate(cell.getNumericCellValue());
                return DateUtil.format(date, "yyyy-MM-dd");
            } else {
                return new DecimalFormat("#.######").format(cell.getNumericCellValue());
            }
        } else if (cellType == Cell.CELL_TYPE_STRING) {
            return StringUtils.trimToEmpty(cell.getRichStringCellValue() + "");
        } else if (cellType == Cell.CELL_TYPE_BLANK) {
            return "";
        } else if (cellType == Cell.CELL_TYPE_BOOLEAN) {
            return String.valueOf(cell.getBooleanCellValue());
        } else if (cellType == Cell.CELL_TYPE_ERROR) {
            return "ERROR";
        } else {
            return cell.toString().trim();
        }
    }

}

然后是封装的两个注解类,也是直接复制使用

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 注释: 读取信息
 *
 * @author yangyongzhuo 2022/11/17 15:41
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Description {

    String description() default "";

    /** 读到空白行处理方式 true 结束此sheet页导入,false继续导入 */
    boolean ifNull() default true;

    /** 表头的位置 */
    int headerIndex() default 0;

    /** 数据行的下表位置 */
    int dataIndex() default 1;

    /** 起始sheet页的下标 */
    int startSheetIndex() default 0;
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 注释: 备注注解
 *
 * @author yangyongzhuo 2022/11/18 10:56
 */
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Excel {

    String value() default "";

    String notes() default "";

}

测试工具类功能

测试实体类

需要注意注解里面的notes是你要导入的字段名称,可以参考我的写法,

/**
 * 注释: 导入对象
 * @Description: dataIndex 数据的行号, 
 * ifNull默认是true 如果有空行就终止导入,false就是忽略空行继续导入
 *
 * @author yangyongzhuo 2022/11/17 15:42
 */
@Description(dataIndex = 9,ifNull = false)
@Data
public class ReadExcelVo {

    @Excel(notes = "监测点编号")
    private String param1;

    @Excel(notes = "上次累计")
    private String param2;

    @Excel(notes = "本次累计")
    private String param3;

    @Excel(notes = "本次变化")
    private String param4;

    @Excel(notes = "变化速率(mm/d)")
    private String param5;

    @Excel(notes = "速率(mm/d)")
    private String param6;

    @Excel(notes = "累计值 (mm)")
    private String param7;

    @Excel(notes = "对应位置\n" +
            "(区域)")
    private String param8;

    @Excel(notes = "备注")
    private String param9;
}

Controller层调用

/**
 * 注释: 文件测试
 *
 * @author yangyongzhuo 2022/11/17 20:01
 */
@RestController
@RequestMapping("/test/controller")
public class TestController {

    @PostMapping("/testImport")
    public void testImport(@RequestParam("file") MultipartFile file) {
        List<ReadExcelVo> readExcelVos = ExcelUtils.readExcel(file, ReadExcelVo.class);
        //打印导入数据
        readExcelVos.forEach(System.err::println);
    }
    
}

执行结果

在这里插入图片描述

如果ifNull 设置为true执行结果

在这里插入图片描述
在这里插入图片描述


仰天大笑出门去,我辈岂是蓬蒿人

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

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

相关文章

Mongodb认证入库并进行多版本缓存使用

作者:yangjunlin 使用过mongodb数据库的小伙伴们都知道&#xff0c;mongodb存储超图缓存是不需要密码的&#xff0c;只需要设置一个用户即可&#xff0c;但部分小伙伴们因为安全问题想用加密模式访问&#xff0c;但是不清楚如何创建&#xff0c;并且想用mongodb库进行多版本缓存…

GitHub 下载量过百万,阿里 P8 秘密分享的「亿级并发系统设计」

随着互联网的不断发展&#xff0c;CPU 硬件的核心数也在不断进步&#xff0c;并发编程越来越普及&#xff0c;但是并发编程并不像其他业务那样直接了当。在编写并发程序时&#xff0c;我们常常都会出现各种漏洞&#xff0c;这些问题往往都突然出现&#xff0c;然后又迅速消失&a…

centos7.9安装MySQL-学习

一、下载mysql5.7安装包 1.下载 二、安装 1、检查系统是否安装过mysql //检查系统中有无安装过mysql rpm -qa|grep mysql 2、查询所有mysql 对应的文件夹&#xff0c;全部删除 whereis mysql find / -name mysql 3、检查mysql 用户组是否存在 cat /etc/group | grep mysql …

Steam项目推进 (一) ——项目情况简述

一、前言 之前跟一个策划朋友一起做过一个小项目Demo&#xff0c;然后中止了大半年&#xff0c;现在想继续把这个项目推进下去。又怕自制力不够&#xff0c;所以建立一个栏目来记录这个项目的推进情况&#xff0c;记忆在项目中学习到的东西。 二、目前的项目情况 1、项目定位…

R-CNN,Fast R-CNN详解

R-CNN R-CNN可以说是利用深度学习进行目标检测的开山之作。作者Ross Girshick多次在PASCAL VOC的目标检测竞赛中折桂&#xff0c;曾在2010年带领团队获得终身成就奖。 R-CNN流程 R-CNN流程可以分为4个步骤&#xff1a; 1.一张图像生成1k~2k个候选区域(使用Selective Search方…

【算法基础】P问题、NP问题、NP-Hard问题、NP-Complete问题

P问题、NP问题、NP-Hard问题、NP-Complete问题前提1. 时间复杂度&#xff1a;2. 约化(Reducibility)P问题NP问题NPHard问题NP-Complete问题其它&#xff1a;前提 1. 时间复杂度&#xff1a; 2. 约化(Reducibility) 如果能找到一个变化法则&#xff0c;对任意一个A程序的输入&…

【华为OD机试真题 python】 转骰子【2022 Q4 | 200分】

■ 题目描述 【转骰子】 骰子是一个立方体,每个面一个数字,初始为左1,右2,前3(观察者方向),后4,上5,下6,用123456表示这个状态,放置在平面上, 可以向左翻转(用L表示向左翻转1次), 可以向右翻转(用R表示向右翻转1次), 可以向前翻转(用F表示向前翻转1次), 可以…

力扣(LeetCode)71. 简化路径(C++)

模拟 对于路径 pathpathpath &#xff0c;遇到 ′/′/′/′ 则操作&#xff0c;遇到其他字符则保存名字。操作有如下几种: 1.名字是 "."".""." 或 """""" 不操作&#xff0c;前者表示在当前目录&#xff0c;后者…

代码管理工具知多少?来看看Git怎么用吧

一. 代码管理工具简介 说到代码工具&#xff0c;许多工作了的小伙伴一定很有发言权。因为我们在实际开发环境中&#xff0c;就代码的复杂度和逻辑度&#xff0c;对于开发工程师来说&#xff0c;都是极具挑战性的。如果单靠个人来完成单个项目的整体开发&#xff0c;那无疑是难…

baostock均线数据怎么描出图形表示?

baostock主要是用量化交易者的一个开放数据的源头系统&#xff0c;其功能可以提供大量准确、完整的证券历史行情数据、上市公司财务数据等服务。利用python API获取证券数据信息&#xff0c;满足量化交易投资者、数量金融爱好者、计量经济从业者数据需求等&#xff0c;同样&…

重点,一文掌握ReentrantLock加解锁原理!|原创

本文详细讲解了 ReentrantLock 加锁和释放锁的原理&#xff0c;以及和 Synchronized 的对比。本文较长&#xff0c;建议收藏&#xff01;点击上方“后端开发技术”&#xff0c;选择“设为星标” &#xff0c;优质资源及时送达简要总结 ReentrantLock实现原理&#xff1a;volati…

Arduino ESP32使用U3115S芯片控制H桥驱动有刷直流电机

Arduino ESP32实现互补PWM输出 问题提出半桥驱动电路必须是PWM互补输入才能工作Arduino ESP32的互补PWM控制问题提出 直流有刷电机控制使用U3115S芯片。芯片是电压高达300V的半H桥驱动电路&#xff0c;管脚说明&#xff1a; NumberSymbolDescription1VCC低侧固定逻辑电源输入…

牛客网——verilog练习题思路汇总

目录 基础语法 VL1 四选一多路器 VL2 异步复位的串联T触发器 VL3 奇偶校验 VL4 移位运算与乘法 VL5 位拆分与运算 VL6 多功能数据处理器 VL7 求两个数的差值 VL8 使用generate…for语句简化代码 VL9 使用子模块实现三输入数的大小比较 VL10 使用函数实现数据大小端转…

智慧城市建设的原则及规划目标

一、建设原则 智慧城市建设过程中必须把握以下原则&#xff1a; &#xff08;一&#xff09; 遵循市信息化建设总体规划与发展框架。遵循 “十二五” 信息化规划确定的“紧紧围绕国家西部大开发战略目标&#xff0c;强化信息技术和信息化在我市加快推进新型工业化进程中的战略…

数据库周期表整体设计方案

这是学习笔记的第 2442篇文章数据库周期表是我们自定义的名称&#xff0c;从数据视角来说&#xff0c;通常会有三类数据表&#xff1a;字典表&#xff0c;状态表&#xff0c;日志表&#xff0c;其中周期表从定位上更侧重于日志表。为什么叫周期表&#xff0c;主要是因为日志数据…

看着2022年世界杯,我无比怀念98世界杯

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> 断断续续看了2022年世界杯部分比赛&#xff0c;我无比怀念1998年世界杯&#xff01;该文权当本人的一点回忆文字&#xff0c;没有经过严谨认证&#xff0…

2023年第三届智能制造与自动化前沿国际会议(CFIMA 2023)

2023年第三届智能制造与自动化前沿国际会议(CFIMA 2023) 重要信息 会议网址&#xff1a;www.cfima.org 会议时间&#xff1a;2023年6月9-11日 召开地点&#xff1a;中国大理 截稿时间&#xff1a;2023年4月20日 录用通知&#xff1a;投稿后2周内 收录检索&#xff1a;EI,…

IDEA创建父子项目

一、搭建父项目 创建第一个maven项目作为父项目 创建完之后建议删掉src文件夹 pom.xml <packaging>POM</packaging><!--依赖版本的锁定--><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</pr…

Linux-Nginx前端项目部署

目录 一、Nginx简介 负载均衡 动静分离 二、Nginx安装 Tomcat负载均衡 准备2个tomcat 修改此三处 修改后保存 启动两个Tomcat 修改Tomcat主界面​编辑 Nginx配置 查看nginx.conf配置 添加/更改配置​编辑 重启Nginx服务&#xff0c;让配置生效 出现权限问题 测试N…

计算机组成原理4小时速成:I/O系统,IO指令,编址方式,串行并行,程序查询传送,中断程序传送,DMA传送,接口组成

计算机组成原理4小时速成&#xff1a;I/O系统&#xff0c;IO指令&#xff0c;编址方式&#xff0c;串行并行&#xff0c;程序查询传送&#xff0c;中断程序传送&#xff0c;DMA传送&#xff0c;接口组成 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#x…