EasyExcel的导入导出使用

news2024/12/26 14:23:40

1、说明

EasyExcel是阿里出的一款基于Java的、快速、简洁、解决大文件内存溢出的处理Excel的开源工具,本文主要是使用这个工具对Excel导入导出进行讲解。

  • 官网: https://easyexcel.opensource.alibaba.com/
  • github: https://github.com/alibaba/easyexcel

2、EasyExcel的使用

2.1、创建工具类

创建工具类EasyExcelUtil,将导出和导入的相关方法写入该类中,以下详细讲述使用EasyExcel导出、导入数据。

2.1.1、EasyExcel导出

2.1.1.1、导出一个sheet-无样式

/**
 * 导出excel表格
 *
 * @param response
 * @param dataList  数据列表
 * @param clazz     数据对象
 * @param fileName  文件名称
 * @param sheetName sheet名称
 * @throws Exception
 */
public static <T> void exportExcel(HttpServletResponse response, List<T> dataList, Class<T> clazz, String fileName, String sheetName) throws Exception {
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
    fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
    response.setHeader("Content-disposition", "attachment;filename=" + fileName + FileTypeEnum.TEMPLATE_SUFFIX.getDesc());
    EasyExcelFactory.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(dataList);
}

2.1.1.2、导出一个sheet-有样式

/**
 * 导出excel表格-支设置单元格样式
 *
 * @param response
 * @param dataList     数据列表
 * @param clazz        数据对象
 * @param fileName     文件名称
 * @param sheetName    sheet名称
 * @param writeHandler 处理器
 * @throws Exception
 */
public static <T> void exportWriteHandlerExcel(HttpServletResponse response, List<T> dataList, Class<T> clazz, String fileName, String sheetName, WriteHandler writeHandler) throws Exception {
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
    fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
    response.setHeader("Content-disposition", "attachment;filename=" + fileName + FileTypeEnum.TEMPLATE_SUFFIX.getDesc());
    ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), clazz).build();
    // 这里注意如果同一个sheet只要创建一次
    WriteSheet writeSheet = EasyExcel.writerSheet(sheetName)
            .registerWriteHandler(writeHandler).build();
    excelWriter.write(dataList, writeSheet);
    excelWriter.finish();
}

2.1.1.3、导出多个sheet

/**
 * 导出多个sheet
 *
 * @param response
 * @param dataList 多个sheet数据列表
 * @param clazzMap 对应每个sheet列表里面的数据对应的sheet名称
 * @param fileName 文件名
 * @param <T>
 * @throws Exception
 */
public static <T> void exportExcels(HttpServletResponse response, List<List<?>> dataList, Map<Integer, String> clazzMap, String fileName) throws Exception {
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
    fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
    response.setHeader("Content-disposition", "attachment;filename=" + fileName + FileTypeEnum.TEMPLATE_SUFFIX.getDesc());
    ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
    int sheetNum = dataList.size();
    for (int i = 0; i < sheetNum; i++) {
        List<?> objects = dataList.get(i);
        Class<?> aClass = objects.get(0).getClass();
        WriteSheet writeSheet0 = EasyExcel.writerSheet(i, clazzMap.get(i)).head(aClass).build();
        excelWriter.write(objects, writeSheet0);
    }
    excelWriter.finish();
}

2.1.1.4、导出手动填充数据的模板

此功能应用于导出同一个业务功能数据,但是每次导出的数据的表头是动态变化的,比如不同租户查看的同一个页面上展示的字段不一样,举个例子,租户A看到展示的字段是a、b、c;租户B看到展示字段是a、b。那么此时导出模板的表头字段就不一样。
<1>导出模板,传入一个处理器

/**
 * 导出excel表格-列不确定情况;只导出模板【一个处理器】
 *
 * @param response
 * @param headList     表头列表
 * @param dataList     数据列表
 * @param fileName     文件名称
 * @param sheetName    sheet名称
 * @param writeHandler 处理器
 * @throws Exception
 */
public static <T> void exportExcel(HttpServletResponse response, List<List<String>> headList, List<List<T>> dataList, 
String fileName, String sheetName, WriteHandler writeHandler) throws Exception {
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
    fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
    response.setHeader("Content-disposition", "attachment;filename=" + fileName + FileTypeEnum.TEMPLATE_SUFFIX.getDesc());
    ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
    // 这里注意如果同一个sheet只要创建一次
    ExcelWriterSheetBuilder sheetBuilder = EasyExcel.writerSheet(sheetName).head(headList);
    if (null != writeHandler) {
        sheetBuilder.registerWriteHandler(writeHandler);
    }
    WriteSheet writeSheet = sheetBuilder.build();
    excelWriter.write(dataList, writeSheet);
    excelWriter.finish();
}

<2>导出模板,传入多个处理器

/**
 * 导出excel表格-列不确定情况;只导出模板【多个处理器】
 *
 * @param response
 * @param headList      表头列表
 * @param dataList      数据列表
 * @param fileName      文件名称
 * @param sheetName     sheet名称
 * @param writeHandlers 处理器集合
 * @throws Exception
 */
public static <T> void exportExcel(HttpServletResponse response, List<List<String>> headList, List<List<T>> dataList, String fileName, String sheetName, List<WriteHandler> writeHandlers) throws Exception {
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
    fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
    response.setHeader("Content-disposition", "attachment;filename=" + fileName + FileTypeEnum.TEMPLATE_SUFFIX.getDesc());
    ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
    // 这里注意如果同一个sheet只要创建一次
    ExcelWriterSheetBuilder sheetBuilder = EasyExcel.writerSheet(sheetName).head(headList);
    if (CollUtil.isNotEmpty(writeHandlers)) {
        for (WriteHandler writeHandler : writeHandlers) {
            sheetBuilder.registerWriteHandler(writeHandler);
        }
    }
    WriteSheet writeSheet = sheetBuilder.build();
    excelWriter.write(dataList, writeSheet);
    excelWriter.finish();
}

2.1.1.5、根据模板导出数据

填充数据的模板示例如下,注意:模板的格式需要加上字段对应的字段属性名称。样式举例:
在这里插入图片描述
说明:. 的{}就是根据自定义实体存值,{}里面是你自己写的字段
<1>填充列表数据

/**
 * 根据模板将集合对象填充表格
 *
 * @param inputStream 模板文件输入流
 * @param response    模板文件输出流
 * @param list        填充对象集合
 * @param fileName    文件名称
 * @param sheetName   需要写入的sheet(不传:填充到第一个sheet)
 * @throws Exception
 */
public static <T> void exportTemplateExcelList(InputStream inputStream, HttpServletResponse response, List<T> list, String fileName, String sheetName) throws Exception {
    // 全部填充:全部加载到内存中一次填充
    if (StringUtils.isBlank(sheetName)) {
        EasyExcelFactory.write(getOutputStream(fileName, response)).withTemplate(inputStream).sheet().doFill(list);
    } else {
        EasyExcelFactory.write(getOutputStream(fileName, response)).withTemplate(inputStream).sheet(sheetName).doFill(list);
    }
}

<2>填充列表+自定义字段属性值数据
此场景适用于模板中的一些字段不是实体中字段,而是一些特殊处理的字段,比如说展示当前文档的一个指示说明,只是特别标注在文档中某一个位置就好了,这个不是集合中的数据,所以自定义字段需要进行特殊处理,他的处理是通过下面方法的入参object。

一个sheet

/**
 * 根据模板将集合对象填充表格-单个sheet
 *
 * @param inputStream 模板文件输入流
 * @param list        填充对象集合-元素对应模板中的.{}
 * @param object      填充对象-元素对应模板中的{}
 * @param fileName    文件名称
 * @throws Exception
 */
public static <T> void exportTemplateSheet(InputStream inputStream, HttpServletResponse response, List<T> list, Object object, String fileName) throws Exception {
    FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
    ExcelWriter excelWriter = EasyExcelFactory.write(getOutputStream(fileName, response)).withTemplate(inputStream).build();
    WriteSheet writeSheet0 = EasyExcelFactory.writerSheet(0).build();
    // 全部填充:全部加载到内存中一次填充
    excelWriter.fill(object, fillConfig, writeSheet0);
    excelWriter.fill(list, fillConfig, writeSheet0);
    // 可分多次填充,使用文件缓存(省内存)
    // excelWriter.fill(list1, writeSheet);
    // excelWriter.fill(list2, writeSheet);
    excelWriter.finish();
}

两个sheet

/**
 * 根据模板将集合对象填充表格-多个sheet
 *
 * @param inputStream 模板文件输入
 * @param response    模板文件输出流
 * @param list1       填充对象集合-元素对应模板中的.{}
 * @param list2
 * @param object1     填充对象-元素对应模板中的{}
 * @param object2
 * @param fileName    文件名称
 * @throws Exception
 */
public static <T> void exportTemplateSheets(InputStream inputStream, HttpServletResponse response, List<T> list1, List<T> list2,
                                            Object object1, Object object2, String fileName) throws Exception {
    FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
    ExcelWriter excelWriter = EasyExcelFactory.write(getOutputStream(fileName, response)).withTemplate(inputStream).build();
    WriteSheet writeSheet0 = EasyExcelFactory.writerSheet(0).build();
    WriteSheet writeSheet1 = EasyExcelFactory.writerSheet(1).build();
    excelWriter.fill(object1, fillConfig, writeSheet0);
    excelWriter.fill(list1, fillConfig, writeSheet0);
    excelWriter.fill(object2, fillConfig, writeSheet1);
    excelWriter.fill(list2, fillConfig, writeSheet1);
    excelWriter.finish();
}

2.1.1.6、补充导出公共方法getOutputStream

/**
 * 构建输出流
 *
 * @param fileName 文件名称
 * @param response 模板文件输出流
 * @param response
 * @return
 * @throws Exception
 */
private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {
    fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
    response.setHeader("Content-Disposition", "attachment;filename=" + fileName + FileTypeEnum.TEMPLATE_SUFFIX.getDesc());
    return response.getOutputStream();
}

2.1.2、EasyExcel导入

2.1.2.1、导入一个sheet

/**
 * 导入-读取一页sheet
 *
 * @param inputStream 文件流
 * @param clazz       数据对象
 * @param sheetName   要读取的sheet (不传,默认读取第一个sheet)
 * @throws Exception
 */
public static <T> List<T> importExcel(InputStream inputStream, Class<T> clazz, String sheetName, ReadExcelListener readExcelListener) throws Exception {
    ExcelReaderBuilder builder = EasyExcelFactory.read(inputStream, clazz, readExcelListener);
    if (StringUtils.isBlank(sheetName)) {
        builder.sheet().doRead();
    } else {
        builder.sheet(sheetName).doRead();
    }
    return readExcelListener.getList();
}

2.1.2.2、导入多个sheet

 /**
     * 导入-读取多个sheet
     *
     * @param inputStream  文件流
     * @param sheetNum     需要读取的sheet个数(默认0开始,如果传入2,则读取0、1)
     * @param sheetObjList 每个sheet里面需要封装的对象(如果index为2,则需要传入对应的2个对象)
     * @param <T>
     * @return
     */
    public static <T> List<List<T>> importExcels(InputStream inputStream, int sheetNum, List<T> sheetObjList, ReadExcelListener<T> readExcelListener) throws Exception {
        List<List<T>> resultList = new LinkedList<>();
        for (int index = 0; index < sheetNum; index++) {
            Class<T> tClass = (Class<T>) sheetObjList.get(index).getClass();
            EasyExcelFactory.read(inputStream, tClass, readExcelListener).sheet(index).doRead();
            List<T> list = readExcelListener.getList();
            resultList.add(list);
        }
        return resultList;
    }

2.2、创建导出样式相关公共类

2.2.1、设置单元格宽度

CellWidthStyleHandler

package com.wm.easyexcel.util;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.Cell;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName: CellWidthStyleHandler
 * @Description: 设置表头的调整宽策略
 * @Author: WM
 * @Date: 2022/12/23 14:37
 */
public class CellWidthStyleHandler extends AbstractColumnWidthStyleStrategy {

    // 可以根据这里的最大宽度,按自己需要进行调整,搭配单元格样式实现类中的,自动换行,效果更好
    private static final int MAX_COLUMN_WIDTH = 50;
    private Map<Integer, Map<Integer, Integer>> CACHE = new HashMap(8);

    /**
     * 是否设置固定宽度
     */
    private boolean fixed;

    /**
     * 固定宽度
     */
    private int fixedWidth;

    public CellWidthStyleHandler() {
    }

    public CellWidthStyleHandler(boolean fixed, int fixedWidth) {
        this.fixed = fixed;
        this.fixedWidth = (fixedWidth == 0 ? 15 : fixedWidth);
    }

    @Override
    protected void setColumnWidth(CellWriteHandlerContext context) {
        boolean needSetWidth = context.getHead() || !CollUtil.isEmpty(context.getCellDataList());
        if (!needSetWidth) {
            return;
        }
        WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
        // 设置固定宽度
        if (fixed) {
            Cell cell = context.getCell();
            writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), fixedWidth * 256);
            return;
        }
        // 设置自动调整宽度
        Map<Integer, Integer> maxColumnWidthMap = CACHE.get(writeSheetHolder.getSheetNo());
        if (maxColumnWidthMap == null) {
            maxColumnWidthMap = new HashMap(16);
            CACHE.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);
        }
        Cell cell = context.getCell();
        int columnWidth = this.dataLength(context.getCellDataList(), cell, context.getHead());
        if (columnWidth >= 0) {
            if (columnWidth > MAX_COLUMN_WIDTH) {
                columnWidth = MAX_COLUMN_WIDTH;
            }
            Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
            if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
                writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
            }
        }
    }

    private int dataLength(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) {
        if (isHead) {
            return cell.getStringCellValue().getBytes().length;
        } else {
            CellData cellData = cellDataList.get(0);
            CellDataTypeEnum type = cellData.getType();
            if (type == null) {
                return -1;
            } else {
                switch (type) {
                    case STRING:
                        return cellData.getStringValue().getBytes().length;
                    case BOOLEAN:
                        return cellData.getBooleanValue().toString().getBytes().length;
                    case NUMBER:
                        return cellData.getNumberValue().toString().getBytes().length;
                    default:
                        return -1;
                }
            }
        }
    }
}

2.2.2、设置默认单元格字体样式

DefaultExcelHeadHandler

package com.wm.easyexcel.util;

import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.IndexedColors;

/**
 * @ClassName: DefaultExcelHeadHandler
 * @Description: 默认自定义表头样式拦截器
 * @Author: WM
 * @Date: 2022/12/22 17:35
 */
public class DefaultExcelHeadHandler extends HorizontalCellStyleStrategy {

    //设置头样式
    @Override
    protected void setHeadCellStyle(CellWriteHandlerContext context) {
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short) 11);
        headWriteFont.setColor(IndexedColors.BLACK.getIndex());
        headWriteCellStyle.setWriteFont(headWriteFont);
        if (stopProcessing(context)) {
            return;
        }
        WriteCellData<?> cellData = context.getFirstCellData();
        WriteCellStyle.merge(headWriteCellStyle, cellData.getOrCreateStyle());
    }
}

ExcelHeadStyles

package com.wm.easyexcel.util;

import lombok.Data;

/**
 * @ClassName: ExcelHeadStyles
 * @Description: excel表头颜色定义
 * @Author: WM
 * @Date: 2022/12/22 17:24
 */
@Data
public class ExcelHeadStyles {

    /**
     * 表头横坐标 - 行
     */
    private Integer rowIndex;

    /**
     * 表头纵坐标 - 列
     */
    private Integer columnIndex;

    /**
     * 内置颜色
     */
    private Short indexColor;

    /**
     * 字体颜色
     */
    private Short fontColor;

    public ExcelHeadStyles(Integer columnIndex, Short fontColor) {
        this.columnIndex = columnIndex;
        this.fontColor = fontColor;
    }

    public ExcelHeadStyles(Integer rowIndex, Integer columnIndex, Short fontColor) {
        this.rowIndex = rowIndex;
        this.columnIndex = columnIndex;
        this.fontColor = fontColor;
    }
}

2.3、创建导入公共监听器

ReadExcelListener

package com.wm.easyexcel.util;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import lombok.extern.slf4j.Slf4j;

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

/**
 * @ClassName: ReadExcelListener
 * @Description: 读取excel文件数据监听器
 * @Author: WM
 * @Date: 2022/12/21 17:00
 */
@Slf4j
public abstract class ReadExcelListener<T> extends AnalysisEventListener<T> {

    private static final int BATCH_COUNT = 10000;

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

    /**
     * 表头数据
     */
    private Map<Integer, String> headMap = null;

    private Map<String, Object> params;

    // 获取数据集
    public List<T> getList() {
        return this.list;
    }

    // 获取表头数据
    public Map<Integer, String> getHeadMap() {
        return this.headMap;
    }

    /**
     * 每条数据都会进入
     *
     * @param object
     * @param analysisContext
     */
    @Override
    public void invoke(T object, AnalysisContext analysisContext) {
        this.list.add(object);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }

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

    /**
     * 读取到的表头信息
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        this.headMap = headMap;
    }

    /**
     * 异常时调用
     *
     * @param exception:
     * @param context:
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        // 数据解析异常
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            throw new RuntimeException("第" + excelDataConvertException.getRowIndex() + "行" + excelDataConvertException.getColumnIndex() + "列" + "数据解析异常");
        }
        // 其他异常...
    }

    /**
     * 数据存储到数据库
     */
    public abstract boolean saveData();
}

这里作为公共导入监听器,保存数据的方法saveData(),下放到各层具体的业务去继承实现。

2.4、创建实体类

创建学生和老师两个实体类
Student

package com.wm.easyexcel.entity;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @ClassName: Student
 * @Description: 学生
 * @Author: WM
 * @Date: 2021-07-22 21:30
 **/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    @ExcelProperty("序号")
    private Integer num;
    @ExcelProperty("学号")
    private String sno;
    @ExcelProperty("学生名字")
    private String name;
    @ExcelProperty("性别")
    private String sex;
}

Teacher

package com.wm.easyexcel.entity;

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

/**
 * @ClassName: Teacher
 * @Description: 老师
 * @Author: WM
 * @Date: 2021-07-22 21:30
 **/
@Data
public class Teacher {
    @ExcelProperty("编号")
    private String sno;
    @ExcelProperty("老师名字")
    private String name;
    @ExcelProperty("性别")
    private String sex;
}

2.5、创建业务接口

IStudentService

package com.wm.easyexcel.service;

import com.wm.easyexcel.entity.Student;

import java.util.List;
import java.util.Map;

/**
 * @ClassName: IStudentService
 * @Description: 学生相关接口
 * @Author: WM
 * @Date: 2023/1/9 16:52
 */
public interface IStudentService {

    /**
     * 查询学生集合信息
     *
     * @return
     */
    List<Student> listStudent(Long limit);

    /**
     * 批量保存学生数据
     *
     * @param students
     * @return
     */
    boolean batchSaveStudent(List<Student> students);

    /**
     * 批量保存学生数据-自行处理行列数据,不使用实体
     *
     * @param dataList
     * @param headMap
     * @return
     */
    boolean batchSaveStudent(List<Map<Integer, String>> dataList, Map<Integer, String> headMap);
}

对应的实现类StudentServiceImpl

package com.wm.easyexcel.service.impl;

import cn.hutool.core.lang.UUID;
import com.wm.easyexcel.entity.Student;
import com.wm.easyexcel.service.IStudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

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

/**
 * @ClassName: StudentServiceImpl
 * @Description: 学生相关接口实现
 * @Author: WM
 * @Date: 2023/1/9 16:53
 */
@Slf4j
@Service
public class StudentServiceImpl implements IStudentService {

    @Override
    public List<Student> listStudent(Long limit) {
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < limit; i++) {
            Student student = new Student();
            student.setSex("男");
            student.setNum(i);
            student.setName("小A" + i);
            student.setSno(UUID.fastUUID().toString());
            students.add(student);
        }
        return students;
    }

    @Override
    public boolean batchSaveStudent(List<Student> students) {
        // 调用dao层接口保存数据...
        log.info("批量保存学生数据,共:{}条数据", students.size());
        return true;
    }

    @Override
    public boolean batchSaveStudent(List<Map<Integer, String>> dataList, Map<Integer, String> headMap) {
        // 这里可以针对读取到的表头数据和对应的行数据,这种应用场景适用于导入的excel中数据需要我们自己解析做其他业务处理,或者表头列是动态的,我们
        // 没有办法使用一个固定的实体做映射
        log.info("表头列数据:{}", headMap.values());
        log.info("批量保存学生数据,共:{}条数据", dataList.size());
        return true;
    }
}

ITeacherService

package com.wm.easyexcel.service;

import com.wm.easyexcel.entity.Teacher;

import java.util.List;
import java.util.Map;

/**
 * @ClassName: ITeacherService
 * @Description: 老师相关接口
 * @Author: WM
 * @Date: 2023/1/9 16:52
 */
public interface ITeacherService {

    /**
     * 查询老师集合信息
     *
     * @return
     */
    List<Teacher> listTeacher(Long limit);

    /**
     * 批量保存老师数据
     *
     * @param Teachers
     * @return
     */
    boolean batchSaveTeacher(List<Teacher> Teachers);

}

对应的实现类TeacherServiceImpl

package com.wm.easyexcel.service.impl;

import cn.hutool.core.lang.UUID;
import com.wm.easyexcel.entity.Teacher;
import com.wm.easyexcel.service.ITeacherService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

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

/**
 * @ClassName: TeacherServiceImpl
 * @Description: 老师相关接口实现
 * @Author: WM
 * @Date: 2023/1/9 16:53
 */
@Slf4j
@Service
public class TeacherServiceImpl implements ITeacherService {

    @Override
    public List<Teacher> listTeacher(Long limit) {
        List<Teacher> teachers = new ArrayList<>();
        for (int i = 0; i < limit; i++) {
            Teacher teacher = new Teacher();
            teacher.setSex("女");
            teacher.setName("老师A" + i);
            teacher.setSno(UUID.fastUUID().toString());
            teachers.add(teacher);
        }
        return teachers;
    }

    @Override
    public boolean batchSaveTeacher(List<Teacher> teachers) {
        // 调用dao层接口保存数据...
        log.info("批量保存老师数据,共:{}条数据", teachers.size());
        return true;
    }
}

2.6、创建Controller

2.6.1、导出ExportController

创建导出的Controller为工具类中各个导出方法的测试做准备。

2.6.1.1、代码展示

package com.wm.easyexcel.controller;

import cn.hutool.core.collection.CollUtil;
import com.wm.easyexcel.entity.Student;
import com.wm.easyexcel.handler.UserExcelHeadHandler;
import com.wm.easyexcel.service.IStudentService;
import com.wm.easyexcel.service.ITeacherService;
import com.wm.easyexcel.strategy.RowMergeStrategy;
import com.wm.easyexcel.util.CellWidthStyleHandler;
import com.wm.easyexcel.util.EasyExcelUtil;
import com.wm.easyexcel.util.ExcelHeadStyles;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ClassName: ExportController
 * @Description: 导出前端控制器
 * @Author: WM
 * @Date: 2023/1/9 16:44
 */
@Slf4j
@RestController
public class ExportController {

    @Resource
    private IStudentService studentService;
    @Resource
    private ITeacherService teacherService;

    private static final String[] USER_HEAD_FIELDS = new String[]{"序号", "学号", "姓名", "性别"};

    /**
     * 导出学生数据-一个sheet
     *
     * @param response
     */
    @GetMapping("/student/export/sheet")
    public void exportSheetStudent(HttpServletResponse response) {
        List<Student> students = studentService.listStudent(10L);
        log.info("单sheet学生数据开始导出...");
        long startTimeL = System.currentTimeMillis();
        try {
            EasyExcelUtil.exportExcel(response, students, Student.class, "导出学生数据-一个sheet", "第一页");
        } catch (Exception e) {
            log.error("单sheet学生数据导出异常!", e);
        }
        log.info("单sheet学生数据导出完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000);
    }

    /**
     * 导出学生数据-多个sheet
     *
     * @param response
     */
    @GetMapping("/student/export/sheets")
    public void exportSheetsStudent(HttpServletResponse response) {
        log.info("多sheet学生数据开始导出...");
        long startTimeL = System.currentTimeMillis();
        try {
            List<List<?>> sheetList = new ArrayList<>();
            List<Student> students1 = studentService.listStudent(10L);
            List<Student> students2 = studentService.listStudent(20L);
            sheetList.add(students1);
            sheetList.add(students2);
            Map<Integer, String> clazzMap = new HashMap<>();
            clazzMap.put(0, "sheet1");
            clazzMap.put(1, "sheet2");
            EasyExcelUtil.exportExcels(response, sheetList, clazzMap, "导出学生数据-多个sheet");
        } catch (Exception e) {
            log.error("多sheet学生数据导出失败!", e);
        }
        log.info("多sheet学生数据导出完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000);
    }


    /**
     * 导出学生数据-重复行合并格式
     *
     * @param response
     */
    @GetMapping("/student/export/mergeRow")
    public void exportMergeRowStudent(HttpServletResponse response) {
        log.info("合并格式学生数据开始导出...");
        long startTimeL = System.currentTimeMillis();
        try {
            List<Student> students = studentService.listStudent(10L);
            List<String> mergeDataList = students.stream().map(Student::getSex).collect(Collectors.toList());
            EasyExcelUtil.exportWriteHandlerExcel(response, students, Student.class, "学生重复数据表格合并", "第一页", new RowMergeStrategy(mergeDataList, 3));
        } catch (Exception e) {
            log.error("合并格式学生数据导出失败!", e);
        }
        log.info("合并格式学生数据导出完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000);
    }

    /**
     * 下载导入学生数据的模板【一个自定义Handler】
     *
     * @param response
     */
    @GetMapping("/student/downTemplate001")
    public void downTemplateStudent001(HttpServletResponse response) {
        log.info("设置单元格宽度-学生数据模板开始下载...");
        long startTimeL = System.currentTimeMillis();
        try {
            List<List<String>> headList = Arrays.stream(USER_HEAD_FIELDS).map(field -> CollUtil.newArrayList(field)).
                    collect(Collectors.toList());
            CellWidthStyleHandler columnWidthHandler = new CellWidthStyleHandler(true, 15);
            EasyExcelUtil.exportExcel(response, headList, Collections.EMPTY_LIST, "学生数据导入模板-设置单元格宽度", "student", columnWidthHandler);
        } catch (Exception e) {
            log.error("设置单元格宽度-学生数据模板下载失败!", e);
        }
        log.info("设置单元格宽度-学生数据模板下载完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000);
    }

    /**
     * 下载导入学生数据的模板【多个自定义Handler】
     *
     * @param response
     */
    @GetMapping("/student/downTemplate002")
    public void downTemplateStudent002(HttpServletResponse response) {
        log.info("设置单元格宽度+字体-学生数据模板开始下载...");
        long startTimeL = System.currentTimeMillis();
        try {
            List<List<String>> headList = Arrays.stream(USER_HEAD_FIELDS).map(field -> CollUtil.newArrayList(field)).
                    collect(Collectors.toList());
            // 表头第一行的指定前3列字标红
            ExcelHeadStyles excelHeadStyle = new ExcelHeadStyles(0, 3, IndexedColors.RED1.getIndex());
            UserExcelHeadHandler headHandler = new UserExcelHeadHandler(excelHeadStyle);
            CellWidthStyleHandler columnWidthHandler = new CellWidthStyleHandler(true, 15);
            EasyExcelUtil.exportExcel(response, headList, Collections.EMPTY_LIST, "学生数据导入模板-设置单元格宽度+字体", "student", CollUtil.newArrayList(headHandler, columnWidthHandler));
        } catch (Exception e) {
            log.error("设置单元格宽度+字体-学生数据模板下载失败!", e);
        }
        log.info("设置单元格宽度+字体-学生数据模板下载完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000);
    }


    /**************以下填充模板数据的接口测试说明:可改为get请求,InputStream可使用本地文件输入流,然后变可通过浏览器下载。******************************/
    /**
     * 根据模板将集合对象填充表格-list
     *
     * @param template
     * @param response
     */
    @PostMapping("/student/template/fill-list")
    public void exportFillTemplateList(@RequestParam("template") MultipartFile template, HttpServletResponse response) {
        log.info("根据模板填充学生数据,list,文件开始下载...");
        long startTimeL = System.currentTimeMillis();
        try {
            List students = studentService.listStudent(10L);
            EasyExcelUtil.exportTemplateExcelList(template.getInputStream(), response, students, "填充学生模板数据-list", "students");
        } catch (Exception e) {
            log.error("根据模板填充学生数据,list,文件下载失败!", e);
        }
        log.info("根据模板填充学生数据,list,文件下载完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000);
    }

    /**
     * 根据模板将集合对象填充表格-单个sheet
     *
     * @param template
     * @param response
     */
    @PostMapping("/student/template/fill-sheet")
    public void exportFillTemplateSheet(@RequestParam("template") MultipartFile template, HttpServletResponse response) {
        log.info("根据模板填充学生数据,单个sheet,文件开始下载...");
        long startTimeL = System.currentTimeMillis();
        try {
            List<Student> students = studentService.listStudent(10L);
            Map<String, Object> map = new HashMap<>(3);
            map.put("startMonth", "2022-8-10");
            map.put("endMonth", "2022-8-12");
            EasyExcelUtil.exportTemplateSheet(template.getInputStream(), response, students, map, "填充学生模板数据-sheet");
        } catch (Exception e) {
            log.error("根据模板填充学生数据,单个sheet,文件下载失败!", e);
        }
        log.info("根据模板填充学生数据,单个sheet,文件下载完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000);
    }

    /**
     * 根据模板将集合对象填充表格-多个sheet
     *
     * @param template
     * @param response
     */
    @PostMapping("/student/template/fill-sheets")
    public void exportFillTemplateSheets(@RequestParam("template") MultipartFile template, HttpServletResponse response) {
        log.info("根据模板填充数据,多个sheet,文件开始下载...");
        long startTimeL = System.currentTimeMillis();
        try {
            List students = studentService.listStudent(10L);
            Map<String, Object> map = new HashMap<>(2);
            map.put("startMonth", "2022-8-10");
            map.put("endMonth", "2022-8-12");
            List teachers = teacherService.listTeacher(10L);
            Map<String, Object> map2 = new HashMap<>(1);
            map2.put("illustrate", "老师人员列表");
            EasyExcelUtil.exportTemplateSheets(template.getInputStream(), response, students, teachers, map, map2, "填充学生模板数据-sheets");
        } catch (Exception e) {
            log.error("根据模板填充数据,多个sheet,文件下载失败!", e);
        }
        log.info("根据模板填充数据,多个sheet,文件下载完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000);
    }

    /********转为get请求测试示例********/
    /**
     * get请求测试
     *
     * @param response
     */
    @GetMapping("/student/template/test")
    public void test(HttpServletResponse response) throws FileNotFoundException {
        File file = new File("C:\\Users\\Administrator\\Desktop\\test.xlsx");
        FileInputStream inputStream = new FileInputStream(file);
        log.info("根据模板填充学生数据,单个sheet,文件开始下载...");
        long startTimeL = System.currentTimeMillis();
        try {
            List<Student> students = studentService.listStudent(10L);
            Map<String, Object> map = new HashMap<>(3);
            map.put("startMonth", "2022-8-10");
            map.put("endMonth", "2022-8-12");
            EasyExcelUtil.exportTemplateSheet(inputStream, response, students, map, "填充学生模板数据-sheet");
        } catch (Exception e) {
            log.error("根据模板填充学生数据,单个sheet,文件下载失败!", e);
        }
        log.info("根据模板填充学生数据,单个sheet,文件下载完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000);
    }
}

2.6.1.2、方法说明

(1)导出学生数据-一个sheet

此方法是简单的做导出数据,导出的excel不做任何格式的修改。
导出结果如下:
在这里插入图片描述

(2)导出学生数据-多个sheet

此方法导出两个sheet的数据,导出的excel不做任何格式的修改。
导出结果如下:
在这里插入图片描述

(3)导出学生数据-重复行合并格式

此方法导出数据中,指定列的相邻行单元格有重复的内容会被合并成一个单元格。这里需要创建一个设定单元格合并策略类RowMergeStrategy:

package com.wm.easyexcel.strategy;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

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

/**
 * @ClassName: RowMergeStrategy
 * @Description: 自定义行合并策略
 * @Author: WM
 * @Date: 2023/1/9 20:01
 */
public class RowMergeStrategy extends AbstractMergeStrategy {

    /**
     * 分组,每几行合并一次
     */
    private List<Integer> exportFieldGroupCountList;

    /**
     * 目标合并列index
     */
    private Integer targetColumnIndex;

    // 需要开始合并单元格的首行index
    private Integer rowIndex;

    // exportDataList为待合并目标列的值
    public RowMergeStrategy(List<String> exportDataList, Integer targetColumnIndex) {
        this.exportFieldGroupCountList = getGroupCountList(exportDataList);
        this.targetColumnIndex = targetColumnIndex;
    }

    @Override
    protected void merge(Sheet sheet, Cell cell, Head head, Integer integer) {
        if (null == rowIndex) {
            rowIndex = cell.getRowIndex();
        }
        // 仅从首行以及目标列的单元格开始合并,忽略其他
        if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == targetColumnIndex) {
            mergeGroupColumn(sheet);
        }
    }

    private void mergeGroupColumn(Sheet sheet) {
        int rowCount = rowIndex;
        for (Integer count : exportFieldGroupCountList) {
            if (count == 1) {
                rowCount += count;
                continue;
            }
            // 合并单元格
            CellRangeAddress cellRangeAddress = new CellRangeAddress(rowCount, rowCount + count - 1, targetColumnIndex, targetColumnIndex);
            sheet.addMergedRegionUnsafe(cellRangeAddress);
            rowCount += count;
        }
    }

    // 该方法将目标列根据值是否相同连续可合并,存储可合并的行数
    private List<Integer> getGroupCountList(List<String> exportDataList) {
        if (CollUtil.isEmpty(exportDataList)) {
            return new ArrayList<>();
        }
        List<Integer> groupCountList = new ArrayList<>();
        int count = 1;
        for (int i = 1; i < exportDataList.size(); i++) {
            if (exportDataList.get(i).equals(exportDataList.get(i - 1))) {
                count++;
            } else {
                groupCountList.add(count);
                count = 1;
            }
        }
        groupCountList.add(count);
        return groupCountList;
    }
}

导出结果如下:
在这里插入图片描述

(4)下载导入学生数据的模板【一个自定义Handler】
  • 这个方法的应用场景是,用户下载自己需要填充数据的模板,这里excel的表头是自定义设定的,可固定也可不固定,我程序里设定的是固定写死的,举个不固定表头的例子:不同租户下用户导出的模板显示的表头数据不同,这个一般是在数据库查出来的设置的。
  • 此外这个方法还设置了单元格的固定宽度样式,所以使用了一个Handler处理器,即CellWidthStyleHandler。
    导出结果如下:在这里插入图片描述
(5)下载导入学生数据的模板【多个自定义Handler】

此方法是在(4)方法的基础上,通过ExcelHeadStyles、UserExcelHeadHandler增加了对表头字体的设置,这里处理的是表头第一行的指定前3列字标红。因为是这是指定业务个人化的一种样式,所以不放入公共样式包里。
UserExcelHeadHandler代码如下:

package com.wm.easyexcel.handler;

import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.wm.easyexcel.util.ExcelHeadStyles;
import org.apache.poi.ss.usermodel.IndexedColors;

/**
 * @ClassName: UserExcelHeadHandler
 * @Description: 用户相关excel处理器
 * @Author: WM
 * @Date: 2023/1/9 18:24
 */
public class UserExcelHeadHandler extends HorizontalCellStyleStrategy {

    private ExcelHeadStyles excelHeadStyle;


    public UserExcelHeadHandler(ExcelHeadStyles excelHeadStyle) {
        this.excelHeadStyle = excelHeadStyle;
    }

    //设置头样式
    @Override
    protected void setHeadCellStyle(CellWriteHandlerContext context) {
        int columnNo = excelHeadStyle.getColumnIndex();
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short) 11);
        if (context.getColumnIndex() <= columnNo) {
            headWriteFont.setColor(excelHeadStyle.getFontColor());
        } else {
            headWriteFont.setColor(IndexedColors.BLACK.getIndex());
        }
        headWriteCellStyle.setWriteFont(headWriteFont);
        if (stopProcessing(context)) {
            return;
        }
        WriteCellData<?> cellData = context.getFirstCellData();
        WriteCellStyle.merge(headWriteCellStyle, cellData.getOrCreateStyle());
    }
}

导出结果如下:
在这里插入图片描述

(6)根据模板将集合对象填充表格-list

此方法是根据上传的模板文件,将查询的数据填充到excel指定的名称的sheet,然后导出。
上传文件的模板(后面根据上传模板导出填充数据都是用的这个模板):
在这里插入图片描述
模板影样式如下:在这里插入图片描述
导出结果如下:
在这里插入图片描述

(7)根据模板将集合对象填充表格-单个sheet

此方法涉及到自定义字段数据的导出,这里是“开始日期”、“结束日期”。
模板样式如下:
在这里插入图片描述
导出结果如下:
在这里插入图片描述

(8)根据模板将集合对象填充表格-多个sheet

此方法导出两个不同集合的数据,指定到对应的sheet。
学生的样式未变,老师模板格式如下:
在这里插入图片描述
导出结果如下:
在这里插入图片描述

2.6.2、导入ImportController

创建导入的Controller为工具类中各个导入方法的测试做准备。

2.6.2.1、代码展示

package com.wm.easyexcel.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.wm.easyexcel.entity.Student;
import com.wm.easyexcel.entity.Teacher;
import com.wm.easyexcel.listern.ImportUserListener;
import com.wm.easyexcel.util.EasyExcelUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName: ImportController
 * @Description: 导入前端控制器
 * @Author: WM
 * @Date: 2023/1/9 16:44
 */
@Slf4j
@RestController
public class ImportController {

    @Resource
    private ImportUserListener importUserListener;

    /**
     * 导入一个sheet学生数据
     *
     * @param file
     */
    @PostMapping("/student/import/sheet")
    public boolean importSheetStudent(@RequestParam("file") MultipartFile file) {
        EasyExcelUtil.checkFile(file);
        log.info("一个sheet学生数据开始导入...");
        long startTime = System.currentTimeMillis();
        List<Student> dataList = null;
        try {
            dataList = EasyExcelUtil.importExcel(file.getInputStream(), null, null, importUserListener);
            // 数据录入
            Assert.isFalse(CollUtil.isEmpty(dataList), "未解析到导入的数据!");
        } catch (Exception e) {
            log.error("一个sheet学生数据导入失败!", e);
        }
        log.info("一个sheet学生数据导入成功,共:{}条,消耗总时间:{}秒", dataList.size(), ((System.currentTimeMillis() - startTime) / 1000));
        return true;
    }

    /**
     * 导入多个sheet学生数据
     *
     * @param file
     */
    @PostMapping("/student/import/sheets")
    public boolean importSheets(@RequestParam("file") MultipartFile file) {
        EasyExcelUtil.checkFile(file);
        log.info("多个sheet数据开始导入...");
        long startTime = System.currentTimeMillis();
        List<Student> dataList = null;
        try {
            List sheetObjList = new ArrayList(2);
            sheetObjList.add(Student.class);
            sheetObjList.add(Teacher.class);
            dataList = EasyExcelUtil.importExcels(file.getInputStream(), 2, sheetObjList, importUserListener);
            // 数据录入
            Assert.isFalse(CollUtil.isEmpty(dataList), "未解析到导入的数据!");
        } catch (Exception e) {
            log.error("多个sheet数据导入失败!", e);
        }
        log.info("多个sheet数据导入成功,共:{}条,消耗总时间:{}秒", dataList.size(), ((System.currentTimeMillis() - startTime) / 1000));
        return true;
    }
}

2.6.2.2、方法说明

(1)导入一个sheet学生数据

此方法是将上传的学生数据excel文件解析,然后解析后的数据保存到数据库中。此处需要使用到的ImportUserListener继承了ReadExcelListener,其主要实现逻辑是将学生或老师的数据进行保存处理。
ImportUserListener代码如下:

package com.wm.easyexcel.listern;

import cn.hutool.core.collection.CollUtil;
import com.wm.easyexcel.entity.Student;
import com.wm.easyexcel.entity.Teacher;
import com.wm.easyexcel.service.IStudentService;
import com.wm.easyexcel.service.ITeacherService;
import com.wm.easyexcel.util.ReadExcelListener;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

/**
 * @ClassName: ImportStudentListener
 * @Description: 导入学生excel数据监听器
 * @Author: WM
 * @Date: 2022/12/22 10:44
 */
@Component
public class ImportUserListener extends ReadExcelListener {

    @Resource
    private IStudentService studentService;
    @Resource
    private ITeacherService teacherService;

    /**
     * 实现数据保存逻辑
     *
     * @return
     */
    @Override
    public boolean saveData() {
        List dataList = getList();
        if (CollUtil.isEmpty(dataList)) {
            return false;
        }
        Object obj = dataList.get(0);
        if (obj instanceof Student) {
            return studentService.batchSaveStudent(dataList);
        } else if (obj instanceof Teacher) {
            return teacherService.batchSaveTeacher(dataList);
        }
        return true;
    }
}
(2)导入多个sheet学生数据

此方法是将excel解析出来的学生和老师数据集合保存于数据库中,这里其实就是在(1)方法的基础上增加了循环处理的操作。

2.6.2.3、导入监听器-动态表头字段

有些场景每次导入数据的表头字段是动态变化的,所以我们不能使用一个固定的实体去和excel表头字段做映射,那么这时候就需要我们自己去处理表头和表数据。这里使用到了ImportDataListener监听器,代码如下:

package com.wm.easyexcel.listern;

import cn.hutool.core.collection.CollUtil;
import com.wm.easyexcel.service.IStudentService;
import com.wm.easyexcel.util.ReadExcelListener;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

/**
 * @ClassName: ImportDataListener
 * @Description: 导入数据监听器
 * @Author: WM
 * @Date: 2023/1/9 19:27
 */
@Component
public class ImportDataListener extends ReadExcelListener {

    @Resource
    private IStudentService studentService;

    /**
     * 实现数据保存逻辑-直接操作表头和行数据
     *
     * @return
     */
    @Override
    public boolean saveData() {
        Map<Integer, String> headMap = getHeadMap();
        List dataList = getList();
        if (CollUtil.isEmpty(headMap) || CollUtil.isEmpty(dataList)) {
            return false;
        }
        return studentService.batchSaveStudent(dataList, headMap);
    }
}

3、测试

3.1、导出测试

3.1.1、总测试结果

最终导出的文件如下所示:
在这里插入图片描述

3.2、导入测试

3.2.1、导入一个sheet学生数据

在这里插入图片描述

3.2.2、导入多个sheet学生数据

在这里插入图片描述

4、总结

本文主要是对EasyExcel导入导出使用的讲解,包括一些导出常用的单元格宽度以及内部字体样式设置,常规的导出以及不固定表头数据的导出,根据上传模板导出数据;常规的根据上传excel文件导入数据,以及根据非固定字段的excel文件上传导入数据。
本文章的demo地址:https://github.com/Deamer1102/excel-demo/tree/master/easyexcel

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

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

相关文章

达梦主备之备库失联后在线恢复加入集群

一、主库故障重启&#xff08;备库接管前重启&#xff09; 主库故障后立即重启&#xff0c;此时主库的守护进程变成 Startup 状态&#xff0c;重新进入守护进程的 启动流程&#xff0c;将数据一致的备库归档设置为有效状态&#xff0c;其余备库归档设置成无效状态&#xff0c;并…

一些工具软件的使用

文章目录010 Editor设置16进制编辑时每行显示的字节数使用列模式编辑VS Code基础操作定义快捷键配置导出/导入列模式将文件中的tab键转换为空格关闭插件自动更新博文链接Beyond Compare文件内容相同依然显示差异过滤文件或文件夹Excel使用组合&#xff0c;进行行、列的折叠使用…

【DX-BT24蓝牙模块-AT命令与手机透传教程】

【DX-BT24蓝牙模块-AT命令与手机透传教程】1. 前言1.1.串口基本参数1.2.AT命令模式和透传模式1.3.模块数据吞吐量2. 接线2.1 模块线序定义2.2 相关AT命令详解2.2.1 命令格式说明2.2.2 回应格式说明2.2.3 AT命令举例说明3. AT命令详解3.1 基础指令3.2 AT指令测试3.3 手机测试4. …

英华特在创业板提交注册:拟募资约5亿元,股权结构较为分散

近日&#xff0c;苏州英华特涡旋技术股份有限公司&#xff08;下称“英华特”&#xff09;在上海证券交易所递交注册。据贝多财经了解&#xff0c;英华特的上市申请于2021年6月30日获得受理&#xff0c;2022年8月25日获得创业板上市委会议通过。 本次冲刺上市&#xff0c;英华特…

智合同丨你还在为填写合同台账犯愁吗?

最近有朋友问我有没有什么智能化手段处理合同台账问题&#xff1f;对方表示合同台账管理实在是太麻烦了&#xff0c;工作量大&#xff0c;占用时间多。答案肯定是有的&#xff0c;那么首先我们来了解下合同台账。合同台账一般指合同台帐&#xff0c;包括合同登记台帐、合同检查…

Solidity 中的数学(第 1 部分:数字)

本文开启了一系列关于在 Solidity 中进行数学运算的文章。第一个要讨论的话题是&#xff1a;数字。 介绍 以太坊是一个可编程的区块链&#xff0c;其功能可以通过将称为智能合约的可执行代码片段发布到区块链本身来扩展。这将以太坊与第一代区块链区分开来&#xff0c;在第一代…

PointNet.pytorch点云三维深度学习论文与代码复现

PointNet.pytorch1. 概要1.1 文章1.2 点云和三维深度学习&#xff0c;PointNet 网络结构1.3 复现说明2. 代码2.1 代码逻辑2.2 思路详解3. 解析组会2023.1.18远程arcivdailyonedb1. 概要 1.1 文章 链接: 论文 1.2 点云和三维深度学习&#xff0c;PointNet 网络结构 链接: 三…

程序员必备素质:代码整洁之道

本次分享的内容是《代码整洁之道》&#xff0c;书中是以现实案例&#xff0c;以讲故事形式来总结归纳问题&#xff0c;并给出解决方案&#xff0c;很容易与我们产生共鸣。文中会有大量书中内容摘抄&#xff0c;都是个人认为很值得分享的内容。当然&#xff0c;也会有个人感悟&a…

【C++】从0到1入门C++编程学习笔记 - 基础入门篇:C++初识

文章目录一、第一个C程序1.1 创建项目1.2 创建文件1.3 编写代码1.4 运行程序二、注释三、变量四、常量五、关键字六、标识符命名规则一、第一个C程序 编写一个C程序总共分为4个步骤 创建项目创建文件编写代码运行程序 1.1 创建项目 Visual Studio是我们用来编写C程序的主要…

【C语言课程设计】通讯录(2.0版本)

前言 在前面的博客中&#xff0c;我们已经了解了通讯录的基本写法。当然那个通讯录是不够完善的。我们本小节对前面的通讯录做一次小升级&#xff0c;添加动态增容模块与利用枚举来优化选择语句 【C语言课程设计】通讯录&#xff08;1.0版本&#xff09;_青色_忘川的博客-CSDN博…

JVM-内存模型详解

JVM 把内存分为若干个不同的区域&#xff0c;这些区域有些是线程私有的&#xff0c;有些则是线程共享的&#xff0c;Java 内存区域也叫做运行时数据区&#xff0c;它的具体划分如下&#xff1a; 虚拟机栈 Java 虚拟机栈是线程私有的数据区&#xff0c;Java 虚拟机栈的生命周期…

word查看技巧:如何快速找到文档的修改痕迹

不知道大家在工作中有没有遇到过这类的工作场景&#xff1a;当初步拟好一份合作协议或是项目策划书后&#xff0c;发给老板或其他同事审阅和修订&#xff0c;通常会不断地来回修改文档。此时&#xff0c;如果你想要查看文档哪里被修改过&#xff1f;你会怎么操作&#xff1f;很…

SpringBoot集成Elasticsearch7.4 实战(二)

1、前言本篇文章主要讲的是:在Springboot环境下&#xff0c;利用JAVA环境操作索引,集成SpringBoot等相关知识2. SpringBoot集成开发工具&#xff0c;这里选择的是IDEA 2019.2&#xff0c;构建Maven工程等一堆通用操作&#xff0c;不清楚的自行百度。2.1. POM配置我这边选择 ela…

协程应用——aiohttp异步爬虫实战

aiohttp异步爬虫实战1. 案例介绍2. 准备工作3. 页面分析4. 实现思路5. 基本配置6. 爬取列表页7. 爬取详情页8. 总结1. 案例介绍 本例要爬取的网站是https://spa5.scrape.center/,数据量相对大&#xff0c;所以用到了异步爬虫&#xff0c;主要学习这种方法是如何提高效率的。网…

Maven学习(三):纯手撸一个Maven项目

纯手撸一个Maven项目一、创建Maven工程目录二、Maven项目构建命令三、插件创建工程1、创建java工程2、创建web工程3、对比java工程和web工程区别一、创建Maven工程目录 按照下图所示的结构创建项目所需文件夹&#xff1a; 在Demo.java文件内输入以下代码&#xff1a; package…

数据库被勒索删除,解决方法

突然数据库被黑了&#xff0c;有一条勒索信息: To recover your lost Database send 0.018 Bitcoin (BTC) to our Bitcoin address: bc1qe4yefrptv2k8shawu3h84j0n8kyvxfk4wwate5 After this, contact us by email with your Server IP or Domain Name and a Proof of Payment …

JavaScript中的严格模式

一.什么是严格模式 在ECMAScript5标准中&#xff0c;JavaScript提出了严格模式的概念&#xff1a; 严格模式是一种具有限制性的JavaScript模式&#xff0c;从而使代码隐式脱离了“懒散(sloppy)模式”&#xff1b;支持严格模式的浏览器在检测到代码中有严格模式时&#xff0c;…

卡方检验的基本原理详解

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录一、卡方检验基本原理1. 1 χ2统计量计算公式1.2 理论频数如何计算&#xff1f;1.3 χ2值的结果如何理解&#xff1f;1.4 χ2检验的自由度如何理解&#xff1f;1.5 χ…

Arduino开发串口控制ESP8266 RGB LED

根据板卡原理RGB三色LED对应引脚&#xff1a;int LEDR12、int LEDG14、int LEDB13;设置串口波特率为115200Serial.begin(115200);源代码如下所示&#xff1a;/*名称&#xff1a;串口控制RGB亮灭实验功能&#xff1a;通过串口输入R、G、B三个字母来点亮对应的LED灯&#xff0c;关…

Java集合进阶——Map

一、Java Map集合详解 Map集合概述和特点 概述&#xff1a; 将键映射到值的对象 一个映射不能包含重复的键 每个键最多只能映射到一个值 Map接口和Collection接口的不同 Map是双列的,Collection是单列的 Map的键唯一,Collection的子体系Set是唯一的 Map集合的数据结构针对键有…