JAVA实现easyExcel批量导入

注解类型描述
ExcelProperty导入指定当前字段对应excel中的那一列。可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,以此类推。千万注意,要么全部不写,要么全部用index,要么全部用名字去匹配。千万别三个混着用,除非你非常了解源代码中三个混着用怎么去排序的。
ExcelIgnore导入默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
DateTimeFormat导入日期转换,用String去接收excel日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat
NumberFormat导入数字转换,用String去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat
ExcelIgnoreUnannotated导入默认不加ExcelProperty 的注解的都会参与读写,加了不会参与

导入方法参数:ReadWorkbook,ReadSheet 都会有的参数,如果为空,默认使用上级。

  • converter 转换器,默认加载了很多转换器。也可以自定义。
  • readListener 监听器,在读取数据的过程中会不断的调用监听器。
  • headRowNumber 需要读的表格有几行头数据。默认有一行头,也就是认为第二行开始起为数据。
  • headclazz二选一。读取文件头对应的列表,会根据列表匹配数据,建议使用class。
  • clazzhead二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据。
  • autoTrim 字符串、表头等数据自动trim
  • password 读的时候是否需要使用密码

ReadWorkbook(理解成excel对象)参数

  • excelType 当前excel的类型 默认会自动判断

  • inputStreamfile二选一。读取文件的流,如果接收到的是流就只用,不用流建议使用file参数。因为使用了inputStream easyexcel会帮忙创建临时文件,最终还是file

  • fileinputStream二选一。读取文件的文件。

  • autoCloseStream 自动关闭流。

  • readCache 默认小于5M用 内存,超过5M会使用 EhCache,这里不建议使用这个参数。

  • useDefaultListener@since 2.1.4默认会加入ModelBuildEventListener来帮忙转换成传入class的对象,设置成false后将不会协助转换对象,自定义的监听器会接收到Map<Integer,CellData>对象,如果还想继续接听到

    class对象,请调用readListener方法,加入自定义的beforeListener、ModelBuildEventListener、 自定义afterListener即可。

ReadSheet(就是excel的一个Sheet)参数

  • sheetNo 需要读取Sheet的编码,建议使用这个来指定读取哪个Sheet
  • sheetName 根据名字去匹配Sheet,excel 2003不支持根据名字去匹配

添加pom依赖

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>easyexcel</artifactId>
   <version>2.2.6</version>
</dependency>
<!--工具类-->
<dependency>
   <groupId>cn.hutool</groupId>
   <artifactId>hutool-all</artifactId>
   <version>5.8.23</version>
</dependency>
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.21</version>
</dependency>
<!--commons依赖  -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.7</version>
</dependency>

第一种:简单导入

实体类

package com.example.mybatismysql8demo.excel;

import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

import java.math.BigDecimal;

@Data
//忽视无注解的字段
@ExcelIgnoreUnannotated
public class GoodsImportExcel {

    /**
     * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
     */
    @ExcelProperty(value = {"商品信息","商品名称"},index = 0)
    public String goodsName;

    @ExcelProperty(value = {"商品信息","商品价格"},index = 1)
    public BigDecimal price;

    @ExcelProperty(value = {"商品信息","商品数量"},index = 2)
    public Integer num;
    
}

监听器

package com.example.mybatismysql8demo.handler;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;

import java.util.List;
import java.util.function.Consumer;

/**
 * 读取excel数据
 */
public class DemoDataListener extends AnalysisEventListener<GoodsImportExcel> {

    /**临时存储正常数据集合,最大存储100*/
    private List<GoodsImportExcel> successDataList = Lists.newArrayListWithExpectedSize(100);

    /**自定义消费者函数接口用于自定义监听器中数据组装*/
    private final Consumer<List<GoodsImportExcel>> successConsumer;

    public DemoDataListener(Consumer<List<GoodsImportExcel>> successConsumer) {
        this.successConsumer = successConsumer;
    }

    @Override
    public void invoke(GoodsImportExcel goodsImportExcel, AnalysisContext analysisContext) {
        successDataList.add(goodsImportExcel);
        System.out.println("数据:"+goodsImportExcel);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        if (CollectionUtils.isNotEmpty(successDataList)) {
            successConsumer.accept(successDataList);
        }
    }
}

执行方法

package com.example.mybatismysql8demo.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.fastjson.JSONObject;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.example.mybatismysql8demo.handler.DemoDataListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.*;


@Slf4j
@RestController
public class EasyExcelController {

    @PostMapping("/easyExcelImport")
    public void importExcel(MultipartFile file,Integer type) {
        if (!file.isEmpty()) {
            //文件名称
            int begin = Objects.requireNonNull(file.getOriginalFilename()).indexOf(".");
            //文件名称长度
            int last = file.getOriginalFilename().length();
            //判断文件格式是否正确
            String fileName = file.getOriginalFilename().substring(begin, last);
            if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
                throw new IllegalArgumentException("上传文件格式错误");
            }
        } else {
            throw new IllegalArgumentException("文件不能为空");
        }
        try (InputStream inputStream = file.getInputStream()) {
            if (type == 1){
                simpleRead(inputStream);
            }else if (type == 2){
                synchronousRead(inputStream);
            }else {
                repeatedRead(inputStream);
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    /**
     * 最简单的读的监听器
     */
    public void simpleRead(InputStream inputStream){
        //获取正确数据
        ArrayList<GoodsImportExcel> successArrayList = new ArrayList<>();
        EasyExcel.read(inputStream)
                .head(GoodsImportExcel.class)
                .registerReadListener(new DemoDataListener(
                        // 监听器中doAfterAllAnalysed执行此方法;所有读取完成之后处理逻辑
                        successArrayList::addAll))
                // 设置sheet,默认读取第一个
                .sheet()
                // 设置标题(字段列表)所在行数
                .headRowNumber(2)
                .doReadSync();
        System.out.println(successArrayList);
    }

    /**
     * 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面
     */
    public void synchronousRead(InputStream inputStream){
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
        List<GoodsImportExcel> batchGoodsImportModels = EasyExcel.read(inputStream)
                .head(GoodsImportExcel.class)
                // 设置sheet,默认读取第一个
                .sheet()
                // 设置标题(字段列表)所在行数
                .headRowNumber(2)
                .doReadSync();
        System.out.println(JSONObject.toJSONString(batchGoodsImportModels));
    }

    /**
     * 读取多个sheet
     */
    public void repeatedRead(InputStream inputStream){
        ArrayList<GoodsImportExcel> successArrayList = new ArrayList<>();
        //使用模型来读取Excel(多个sheet)
        ExcelReader reader = EasyExcel.read(inputStream).build();
        //多个sheet
        List<ReadSheet> sheetList = new ArrayList<>();
        for (int i = 0; i < reader.getSheets().size(); i++){
            // 这里为了简单,所以注册了同样的head 和Listener 自己使用功能必须不同的Listener
            ReadSheet readSheet = EasyExcel.readSheet(i)
                    .head(GoodsImportExcel.class)
                    .registerReadListener(new DemoDataListener(successArrayList::addAll))
                    // 设置标题(字段列表)所在行数
                    .headRowNumber(2)
                    .build();
            sheetList.add(readSheet);
        }
        // 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
        reader.read(sheetList);
        // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
        reader.finish();
        System.out.println(successArrayList);
    }
    
}

结果打印
数据:GoodsImportExcel(goodsName=苹果, price=10, num=11)
数据:GoodsImportExcel(goodsName=香蕉, price=8, num=12)
数据:GoodsImportExcel(goodsName=梨子, price=11.0, num=30)
数据:GoodsImportExcel(goodsName=葡萄, price=20.0, num=40)
[GoodsImportExcel(goodsName=苹果, price=10, num=11), GoodsImportExcel(goodsName=香蕉, price=8, num=12), GoodsImportExcel(goodsName=梨子, price=11.0, num=30), GoodsImportExcel(goodsName=葡萄, price=20.0, num=40)]

导入模版
在这里插入图片描述
在这里插入图片描述

第二种:数据校验

自定义注解

package com.example.mybatismysql8demo.config;

import java.lang.annotation.*;


@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface LengthValid {

    int length() default 0;

    String msg() default "";

    int cell() default 0;
}

实体类

package com.example.mybatismysql8demo.excel;

import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.example.mybatismysql8demo.config.LengthValid;
import lombok.Data;

import java.math.BigDecimal;

@Data
//忽视无注解的字段
@ExcelIgnoreUnannotated
public class GoodsImportExcel {

    /**
     * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
     */
    @LengthValid(length =  5,msg = "商品名称长度超出5个字符串!",cell = 1)
    @ExcelProperty(value = {"商品信息","商品名称"},index = 0)
    public String goodsName;

    @ExcelProperty(value = {"商品信息","商品价格"},index = 1)
    public BigDecimal price;

    @ExcelProperty(value = {"商品信息","商品数量"},index = 2)
    public Integer num;

    private String errorMsg;
}

监听器

package com.example.mybatismysql8demo.handler;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.api.Assert;
import com.example.mybatismysql8demo.config.LengthValid;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

/**
 * 读取excel数据
 */
@Slf4j
public class DemoDataListener extends AnalysisEventListener<GoodsImportExcel> {

    /**单次处理上限100条记录*/
    private static final int BATCH_COUNT = 100;

    /**临时存储正常数据集合*/
    private List<GoodsImportExcel> successDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);

    /**临时存错误储数据集合*/
    private List<GoodsImportExcel> errorDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);

    /**自定义消费者函数接口用于自定义监听器中数据组装*/
    private final Consumer<List<GoodsImportExcel>> successConsumer;

    private final Consumer<List<GoodsImportExcel>> errorConsumer;

    public DemoDataListener(Consumer<List<GoodsImportExcel>> successConsumer, Consumer<List<GoodsImportExcel>> errorConsumer) {
        this.successConsumer = successConsumer;
        this.errorConsumer = errorConsumer;
    }

    /**手机号格式异常日志处理*/
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.error("异常信息:{}", exception.getMessage());
        // 如果是某一个单元格的转换异常 能获取到具体行号,如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
        }else if (exception instanceof IllegalArgumentException){
            throw new IllegalArgumentException(exception.getMessage());
        }
    }

    /**
     * 在这里进行模板的判断
     * @param headMap 存放着导入表格的表头,键是索引,值是名称
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        //只校验第三行表头是否正确
        Integer rowNum = context.getCurrentRowNum();
        if (rowNum == 2) {
            // 获取数据实体的字段列表
            Field[] fields = GoodsImportExcel.class.getDeclaredFields();
            // 遍历字段进行判断
            for (Field field : fields) {
                // 获取当前字段上的ExcelProperty注解信息
                ExcelProperty fieldAnnotation = field.getAnnotation(ExcelProperty.class);
                // 判断当前字段上是否存在ExcelProperty注解
                if (fieldAnnotation != null) {
                    String value = fieldAnnotation.value()[1];
                    // 存在ExcelProperty注解则根据注解的value值到表格中对比是否存在对应的表头
                    if(!headMap.containsValue(value)){
                        // 如果表格不包含模版类字段中的表头,则抛出异常不再往下执行
                        throw new RuntimeException("模板错误,请检查导入模板");
                    }
                }
            }
        }
    }


    /**每行读取监听触发逻辑*/
    @SneakyThrows
    @Override
    public void invoke(GoodsImportExcel goodsImportExcel, AnalysisContext analysisContext) {
        //获取总行数
        Integer rowNumber = analysisContext.readSheetHolder().getApproximateTotalRowNumber();
        //行数
        int row = analysisContext.readRowHolder().getRowIndex();
        log.info("第" + row + "行数据进行处理");
        // 手机号格式校验
        validParam(goodsImportExcel,row);
        //正常数据
        successDataList.add(goodsImportExcel);
        // 按照指定条数对导入数据进行分批处理
        if (successDataList.size() >= BATCH_COUNT) {
            successConsumer.accept(successDataList);
            successDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    private void validParam(GoodsImportExcel goodsImportExcel, int row) throws IllegalAccessException {
        // 参数校验
        Field[] fields = goodsImportExcel.getClass().getDeclaredFields();
        for (Field field : fields) {
            //设置可访问
            field.setAccessible(true);
            //判断字段是否添加校验
            boolean valid = field.isAnnotationPresent(LengthValid.class);
            if (valid) {
                //获取注解信息
                LengthValid annotation = field.getAnnotation(LengthValid.class);
                //行数列数
                String msg = "第" + row + "行的第" + annotation.cell() + "列:";
                //值
                String value = (String) field.get(goodsImportExcel);
                if(value.length() > annotation.length()){
                    //错误信息
                    goodsImportExcel.setErrorMsg(msg + annotation.msg());
                    //错误数据
                    errorDataList.add(goodsImportExcel);
                    // 按照指定条数对导入数据进行分批处理
                    if (errorDataList.size() >= BATCH_COUNT) {
                        errorConsumer.accept(errorDataList);
                        errorDataList = Lists.newArrayListWithExpectedSize(BATCH_COUNT);
                    }
                    throw new RuntimeException(msg + annotation.msg());
                }
            }
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        if (CollectionUtils.isNotEmpty(successDataList)) {
            successConsumer.accept(successDataList);
        }
        if (CollectionUtils.isNotEmpty(errorDataList)) {
            errorConsumer.accept(errorDataList);
        }
    }

    /**
     * 额外信息(批注、超链接、合并单元格信息读取)
     */
    @Override
    public void extra(CellExtra extra, AnalysisContext context) {
        log.info("读取到了一条额外信息:{}", JSONObject.toJSONString(extra));
        switch (extra.getType()) {
            case COMMENT:
                log.info("额外信息是批注,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(), extra.getText());
                break;
            case HYPERLINK:
                if ("Sheet1!A1".equals(extra.getText())) {
                    log.info("额外信息是超链接,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(), extra.getText());
                } else if ("Sheet2!A1".equals(extra.getText())) {
                    log.info("额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}," + "内容是:{}",
                            extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
                            extra.getLastColumnIndex(), extra.getText());
                } else {
                    Assert.fail("Unknown hyperlink!");
                }
                break;
            case MERGE:
                log.info("额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",
                        extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
                        extra.getLastColumnIndex());
                break;
            default:
        }
    }

    /**
     *监听器的hasNext()方法时没有注意到默认返回的是false,导致一进监听器就判断已经没有下一条记录,直接跳出监听器,然后导入就完成,也不会报错,改成返回true即可解决
     */
    @Override
    public boolean hasNext(AnalysisContext analysisContext) {
        return true;
    }
}

执行方法

package com.example.mybatismysql8demo.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.fastjson.JSONObject;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.example.mybatismysql8demo.handler.DemoDataListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.*;


@Slf4j
@RestController
public class EasyExcelController {

    @PostMapping("/easyExcelImport")
    public void importExcel(MultipartFile file,Integer type) {
        if (!file.isEmpty()) {
            //文件名称
            int begin = Objects.requireNonNull(file.getOriginalFilename()).indexOf(".");
            //文件名称长度
            int last = file.getOriginalFilename().length();
            //判断文件格式是否正确
            String fileName = file.getOriginalFilename().substring(begin, last);
            if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
                throw new IllegalArgumentException("上传文件格式错误");
            }
        } else {
            throw new IllegalArgumentException("文件不能为空");
        }
        try (InputStream inputStream = file.getInputStream()) {
            if (type == 1){
                simpleRead(inputStream);
            }else if (type == 2){
                synchronousRead(inputStream);
            }else {
                repeatedRead(inputStream);
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    /**
     * 最简单的读的监听器
     */
    public void simpleRead(InputStream inputStream){
        //获取正确数据
        ArrayList<GoodsImportExcel> successArrayList = new ArrayList<>();
        //获取错误数据
        ArrayList<GoodsImportExcel> errorArrayList = new ArrayList<>();
        EasyExcel.read(inputStream)
                .head(GoodsImportExcel.class)
                .registerReadListener(new DemoDataListener(
                        // 监听器中doAfterAllAnalysed执行此方法;所有读取完成之后处理逻辑
                        successArrayList::addAll, errorArrayList::addAll))
                // 设置sheet,默认读取第一个
                .sheet()
                // 设置标题(字段列表)所在行数
                .headRowNumber(2)
                .doReadSync();
        System.out.println(successArrayList);
        System.out.println(errorArrayList);
    }

    /**
     * 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面
     */
    public void synchronousRead(InputStream inputStream){
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
        List<GoodsImportExcel> batchGoodsImportModels = EasyExcel.read(inputStream)
                .head(GoodsImportExcel.class)
                // 设置sheet,默认读取第一个
                .sheet()
                // 设置标题(字段列表)所在行数
                .headRowNumber(2)
                .doReadSync();
        System.out.println(JSONObject.toJSONString(batchGoodsImportModels));
    }

    /**
     * 读取多个sheet
     */
    public void repeatedRead(InputStream inputStream){
        ArrayList<GoodsImportExcel> successArrayList = new ArrayList<>();
        //获取错误数据
        ArrayList<GoodsImportExcel> errorArrayList = new ArrayList<>();
        //使用模型来读取Excel(多个sheet)
        ExcelReader reader = EasyExcel.read(inputStream).build();
        //多个sheet
        List<ReadSheet> sheetList = new ArrayList<>();
        for (int i = 0; i < reader.getSheets().size(); i++){
            // 这里为了简单,所以注册了同样的head 和Listener 自己使用功能必须不同的Listener
            ReadSheet readSheet = EasyExcel.readSheet(i)
                    .head(GoodsImportExcel.class)
                    .registerReadListener(new DemoDataListener(successArrayList::addAll, errorArrayList::addAll))
                    // 设置标题(字段列表)所在行数
                    .headRowNumber(2)
                    .build();
            sheetList.add(readSheet);
        }
        // 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
        reader.read(sheetList);
        // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
        reader.finish();
        System.out.println(successArrayList);
        System.out.println(errorArrayList);
    }
}

结果打印
[GoodsImportExcel(goodsName=苹果, price=10, num=11, errorMsg=null), GoodsImportExcel(goodsName=香蕉, price=8, num=12, errorMsg=null), GoodsImportExcel(goodsName=葡萄, price=20.0, num=40, errorMsg=null)]
[GoodsImportExcel(goodsName=梨子1111, price=11.0, num=30, errorMsg=2行的第1:商品名称长度超出5个字符串!)]

导入模版
在这里插入图片描述

第三种:读取存在合并

监听器

package com.example.mybatismysql8demo.handler;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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



/**
 * Excel模板的读取监听类
 * @author gd
 */
public class ImportExcelListener<T> extends AnalysisEventListener<T> {

    private static final Logger LOGGER = LoggerFactory.getLogger(ImportExcelListener.class);

    /**
     * 解析的数据
     */
    private final List<T> list = new ArrayList<>();

    /**
     * 正文起始行
     */
    private final Integer headRowNumber;

    /**
     * 合并单元格
     */
    private final List<CellExtra> extraMergeInfoList = new ArrayList<>();


    public ImportExcelListener(Integer headRowNumber) {
        this.headRowNumber = headRowNumber;
    }

    /**
     * 这个每一条数据解析都会来调用
     */
    @Override
    public void invoke(T data, AnalysisContext context) {
        LOGGER.info("数据处理: " + JSON.toJSONString(data));
        list.add(data);
    }

    /**
     * 所有数据解析完成了 都会来调用
     * @param context context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        LOGGER.info("所有数据解析完成!");
    }


    /**
     * 返回解析出来的List
     */
    public List<T> getData() {
        return list;
    }


    /**
     * 读取额外信息:合并单元格
     */
    @Override
    public void extra(CellExtra extra, AnalysisContext context) {
        LOGGER.info("读取到了一条额外信息:{}", JSON.toJSONString(extra));
        switch (extra.getType()) {
            case COMMENT:
                LOGGER.info("额外信息是批注,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(), extra.getText());
                break;
            case MERGE: {
                LOGGER.info(
                        "额外信息是合并单元格,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",
                        extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(), extra.getLastColumnIndex());
                if (extra.getRowIndex() >= headRowNumber) {
                    extraMergeInfoList.add(extra);
                }
                break;
            }
            default:
        }
    }

    /**
     * 返回解析出来的合并单元格List
     */
    public List<CellExtra> getExtraMergeInfoList() {
        return extraMergeInfoList;
    }
}

合并数据处理工具类

package com.example.mybatismysql8demo.utils;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.util.CollectionUtils;
import com.example.mybatismysql8demo.config.LengthValid;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.example.mybatismysql8demo.handler.ImportExcelListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.List;

public class ImportExcelMergeUtil<T> {

    private static final Logger LOGGER = LoggerFactory.getLogger(ImportExcelMergeUtil.class);

    /**
     * 返回解析后的List
     *
     * @param: fileName 文件名
     * @param: clazz Excel对应属性名
     * @param: sheetNo 要解析的sheet
     * @param: headRowNumber 正文起始行
     * @return java.util.List<T> 解析后的List
     */
    public void getList(InputStream inputStream, Class<GoodsImportExcel> clazz, Integer sheetNo, Integer headRowNumber,List<T> successList,List<T> errorList) {
        ImportExcelListener<T> listener = new ImportExcelListener<>(headRowNumber);
        try {
            EasyExcel.read(inputStream, clazz, listener).extraRead(CellExtraTypeEnum.MERGE).sheet(sheetNo).headRowNumber(headRowNumber).doRead();
        } catch (Exception e) {
            LOGGER.error(e.getMessage());
        }
        List<CellExtra> extraMergeInfoList = listener.getExtraMergeInfoList();
        //解析数据
        List<T> list;
        if (CollectionUtils.isEmpty(extraMergeInfoList)) {
            list = (listener.getData());
        }else {
            list = explainMergeData(listener.getData(), extraMergeInfoList, headRowNumber);
        }
        //数据处理
        for (T v : list) {
            if(validParam(v)){
                errorList.add(v);
            }else {
                successList.add(v);
            }
        }
    }

    private Boolean validParam(T object){
        // 参数校验
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            //设置可访问
            field.setAccessible(true);
            //判断字段是否添加校验
            boolean valid = field.isAnnotationPresent(LengthValid.class);
            if (valid) {
                try {
                    //获取注解信息
                    LengthValid annotation = field.getAnnotation(LengthValid.class);
                    //值
                    String value = (String) field.get(object);
                    if(value.length() > annotation.length()){
                        //错误信息(需要设置字段为public)
                        Field errorMsg = object.getClass().getField("errorMsg");
                        if (errorMsg.get(object) == null){
                            errorMsg.set(object, annotation.msg());
                        }else {
                            errorMsg.set(object,errorMsg.get(object) + "," + annotation.msg());
                        }
                        return true;
                    }
                } catch (IllegalAccessException | NoSuchFieldException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    /**
     * 处理合并单元格
     * @param data               解析数据
     * @param extraMergeInfoList 合并单元格信息
     * @param headRowNumber      起始行
     * @return 填充好的解析数据
     */
    private List<T> explainMergeData(List<T> data, List<CellExtra> extraMergeInfoList, Integer headRowNumber) {
        //循环所有合并单元格信息
        extraMergeInfoList.forEach(cellExtra -> {
            int firstRowIndex = cellExtra.getFirstRowIndex() - headRowNumber;
            int lastRowIndex = cellExtra.getLastRowIndex() - headRowNumber;
            int firstColumnIndex = cellExtra.getFirstColumnIndex();
            int lastColumnIndex = cellExtra.getLastColumnIndex();
            //获取初始值
            Object initValue = getInitValueFromList(firstRowIndex, firstColumnIndex, data);
            //设置值
            for (int i = firstRowIndex; i <= lastRowIndex; i++) {
                for (int j = firstColumnIndex; j <= lastColumnIndex; j++) {
                    setInitValueToList(initValue, i, j, data);
                }
            }
        });
        return data;
    }

    /**
     * 设置合并单元格的值
     *
     * @param filedValue  值
     * @param rowIndex    行
     * @param columnIndex 列
     * @param data        解析数据
     */
    private void setInitValueToList(Object filedValue, Integer rowIndex, Integer columnIndex, List<T> data) {
        T object = data.get(rowIndex);
        for (Field field : object.getClass().getDeclaredFields()) {
            //提升反射性能,关闭安全检查
            field.setAccessible(true);
            ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
            if (annotation != null) {
                if (annotation.index() == columnIndex) {
                    try {
                        field.set(object, filedValue);
                        break;
                    } catch (IllegalAccessException e) {
                        LOGGER.error("设置合并单元格的值异常:"+e.getMessage());
                    }
                }
            }
        }
    }


    /**
     * 获取合并单元格的初始值
     * rowIndex对应list的索引
     * columnIndex对应实体内的字段
     * @param firstRowIndex    起始行
     * @param firstColumnIndex 起始列
     * @param data             列数据
     * @return 初始值
     */
    private Object getInitValueFromList(Integer firstRowIndex, Integer firstColumnIndex, List<T> data) {
        Object filedValue = null;
        T object = data.get(firstRowIndex);
        for (Field field : object.getClass().getDeclaredFields()) {
            //提升反射性能,关闭安全检查
            field.setAccessible(true);
            ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
            if (annotation != null) {
                if (annotation.index() == firstColumnIndex) {
                    try {
                        filedValue = field.get(object);
                        break;
                    } catch (IllegalAccessException e) {
                        LOGGER.error("获取合并单元格的初始值异常:"+e.getMessage());
                    }
                }
            }
        }
        return filedValue;
    }
}

执行方法

package com.example.mybatismysql8demo.controller;

import com.alibaba.fastjson.JSONObject;
import com.example.mybatismysql8demo.excel.GoodsImportExcel;
import com.example.mybatismysql8demo.utils.ImportExcelMergeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.*;


@Slf4j
@RestController
public class EasyExcelController {

    @PostMapping("/easyExcelImport")
    public void importExcel(MultipartFile file) {
        if (!file.isEmpty()) {
            //文件名称
            int begin = Objects.requireNonNull(file.getOriginalFilename()).indexOf(".");
            //文件名称长度
            int last = file.getOriginalFilename().length();
            //判断文件格式是否正确
            String fileName = file.getOriginalFilename().substring(begin, last);
            if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
                throw new IllegalArgumentException("上传文件格式错误");
            }
        } else {
            throw new IllegalArgumentException("文件不能为空");
        }
        ImportExcelMergeUtil<GoodsImportExcel> helper = new ImportExcelMergeUtil<>();
        List<GoodsImportExcel> successList = new ArrayList<>();
        List<GoodsImportExcel> errorList = new ArrayList<>();
        try {
            helper.getList(file.getInputStream(), GoodsImportExcel.class,0,2,successList,errorList);
            System.out.println("正确数据:"+successList);
            System.out.println("错误数据:"+errorList);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

结果打印
正确数据:[GoodsImportExcel(goodsName=香蕉, price=8, num=12, errorMsg=null)]
错误数据:[GoodsImportExcel(goodsName=苹果11111, price=10, num=11, errorMsg=商品名称长度超出5个字符串!), GoodsImportExcel(goodsName=苹果11111, price=9.0, num=20, errorMsg=商品名称长度超出5个字符串!)]

导入模版
在这里插入图片描述

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
EasyExcel 是一款基于 JavaExcel 操作工具,可以方便地进行 Excel 的读写操作。以下是使用 EasyExcel 批量导入实现的步骤: 1. 引入 EasyExcel 依赖 ```xml <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.3.0</version> </dependency> ``` 2. 创建实体类 根据 Excel 中的列名和顺序,创建与之对应的实体类。 3. 创建 ExcelListener 继承 AnalysisEventListener 类,重写 invoke 方法,将读取到的 Excel 数据封装到实体类中。 ```java public class ExcelListener extends AnalysisEventListener<User> { private List<User> userList = new ArrayList<>(); @Override public void invoke(User user, AnalysisContext analysisContext) { userList.add(user); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { // 数据处理完毕后的操作 } public List<User> getUserList() { return userList; } } ``` 4. 执行导入操作 通过 EasyExcel 提供的 ReadExcel 接口,将 Excel 文件读取并解析,将数据封装到实体类中。 ```java // 构建 Excel 文件输入流 InputStream inputStream = new FileInputStream("user.xlsx"); // 构建 Excel Listener ExcelListener excelListener = new ExcelListener(); // 构建 ExcelReader ExcelReader excelReader = new ExcelReader(inputStream, null, excelListener); // 读取 Excel 文件 excelReader.read(); // 获取读取到的数据 List<User> userList = excelListener.getUserList(); ``` 5. 数据入库 将读取到的数据入库。 以上就是使用 EasyExcel 实现批量导入的步骤,代码示例中的 User 类是一个示例,实际使用时需要根据 Excel 中的列名和顺序创建对应的实体类。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值