easyexcel抽象的数据处理器

package com.XX.XX.util.easyexcel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * AbstractExcelImportExecutor
 *
 * @author wangchen
 * @date 2022/8/4 19:53
 */
@Slf4j
public abstract class AbstractExcelImportExecutor<T extends BaseImportModel> {

    public String handle(MultipartFile file) {
        Class<T> clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        ExcelImportContext context = new ExcelImportContext();
        context.setFileName(Objects.requireNonNull(file.getOriginalFilename()).substring(0, file.getOriginalFilename().lastIndexOf(".")));
        UploadDataListener<T> listener = new UploadDataListener<>(this, context);
        try {
            // 读取Excel数据
            EasyExcel.read(file.getInputStream(), clazz, listener).sheet().doRead();
        } catch (IOException e) {
            log.error("", e);
        }
        return context.getExportFileName();
    }

    /**
     * 数据处理
     *
     * @param dataList 解析的数据
     * @param context  上下文
     */
    protected abstract void execute(List<T> dataList, ExcelImportContext context);

    /**
     * 错误文件提示下载
     *
     * @param dataList 错误数据
     * @param context  上下文
     * @param isCache  是否缓存错误文件多次下载
     * @author hao.chen
     * @time 2023/2/10 13:55
     */
    protected void exceptionHandle(List<T> dataList, ExcelImportContext context, boolean isCache) {
        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }
        Class<T> clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        String fileName = context.getFileName();
        Consumer<ExcelWriter> consumer = excelWriter -> {
            WriteSheet writeSheet = EasyExcel.writerSheet()
                    .head(clazz)
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .build();
            excelWriter.write(dataList, writeSheet);
        };
        if (isCache) {
            context.setExportFileName(ExcelExportUtils.excelExportTemporary(fileName, consumer));
        } else {
            ExcelExportUtils.excelExport(fileName, consumer);
        }
    }
}
package com.XX.XX.util.easyexcel;

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

/**
 * BaseImportModel
 *
 * @author wangchen
 * @date 2022/8/4 19:55
 */
@Data
public class BaseImportModel {

    /** 异常消息 */
    @ExcelProperty(value = "errorMsg")
    private String errorMessage;
}
package com.XX.XX.util.easyexcel;

import lombok.Data;

/**
 * ExcelImportContext
 *
 * @author wangchen
 * @date 2022/10/19 18:24
 */
@Data
public class ExcelImportContext {

    private String fileName;

    private boolean isComplete;

    private String exportFileName;

}
package com.XX.XX.util.easyexcel;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;

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

/**
 * UploadDataListener
 *
 * @author wangchen
 * @date 2022/8/4 19:59
 */
@Slf4j
public class UploadDataListener<T extends BaseImportModel> extends AnalysisEventListener<T> {

    /** 每1000条数据保存,实际使用中可以3000条,然后清理list ,方便内存回收 */
    private static final int BATCH_COUNT = 1000;

    /** 缓存的数据 */
    private final List<T> cachedDataList = new ArrayList<>(BATCH_COUNT);

    private final AbstractExcelImportExecutor<T> abstractExcelImportExecutor;
    private final ExcelImportContext context;


    public UploadDataListener(AbstractExcelImportExecutor<T> abstractExcelImportExecutor, ExcelImportContext context) {
        this.abstractExcelImportExecutor = abstractExcelImportExecutor;
        this.context = context;
    }


    /**
     * 读取数据
     *
     * @param data    传入数据
     * @param context 读取数据
     */
    @Override
    public void invoke(T data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        cachedDataList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData(false);
            // 存储完成清理 list
            cachedDataList.clear();
        }
    }


    /**
     * sheet读取完会执行
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData(true);
        log.info("所有数据解析完成!");
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        log.info("表头数据 excelHead= {}", headMap);
    }


    /**
     * 存储数据  注意对先后顺序要求的导入
     */
    @Async
    public void saveData(boolean isComplete) {
        log.info("{}条数据,开始处理!", cachedDataList.size());
        context.setComplete(isComplete);
        abstractExcelImportExecutor.execute(cachedDataList, context);
        log.info("处理完成!");
    }
}

<think>好的,我现在需要帮助用户解决EasyExcel填充数据缓慢的问题。首先,我得回顾一下用户的历史对话,看看之前有没有相关的信息。用户之前询问过如何使用并行流优化分页处理,可能是在处理大数据量时遇到了性能问题。现在用户的问题是关于EasyExcel填充数据缓慢,这可能也是大数据量导出时的常见问题。 首先,我需要分析EasyExcel速度慢的可能原因。根据经验,常见的原因包括数据读取方式不当、模板设计复杂、频繁的IO操作、内存不足以及同步写入等。用户可能在使用EasyExcel时没有采用合适的优化措施,导致处理大数据时效率低下。 接下来,我需要考虑如何组织回答。用户可能需要一个结构化的解决方案,包含问题原因和对应的优化策略。我应该分点列出问题,然后给出具体的解决方案,每个方案下再细分步骤或代码示例,这样用户更容易理解和实施。 然后,我需要回忆EasyExcel的官方文档和最佳实践。例如,EasyExcel推荐使用异步读取、模板缓存、分页查询等方法来提升性能。此外,使用并发填充和调整JVM参数也是常见的优化手段。可能还需要提醒用户避免在模板中使用过多的公式和样式,因为这些会增加处理时间。 同时,用户之前使用过并行流处理分页,所以可能对并发处理有一定的了解。这时候可以建议他们在EasyExcel中使用并发填充,但需要注意线程安全和资源控制。例如,使用自定义线程池来管理并发任务,避免过度消耗系统资源。 另外,用户可能没有正确使用缓存,导致每次填充都重新解析模板,这会显著降低速度。应该提醒他们启用模板缓存,特别是在多次填充相同模板时,重复利用已解析的模板结构。 内存管理也是一个关键点。处理大数据时,如果一次性加载所有数据到内存,可能导致OOM错误。应该建议分页查询数据,分批填充,或者使用磁盘缓存来减少内存压力。同时,调整JVM参数如增加堆内存大小和使用G1垃圾回收器,可以改善性能。 还需要考虑模板设计的优化。复杂的模板,特别是带有大量公式、合并单元格和条件样式的,会显著增加处理时间。建议用户简化模板,使用占位符替代复杂样式,或者将样式处理移到代码中,减少模板的复杂度。 异常处理和日志记录也是需要注意的地方。过多的日志输出会影响性能,应该建议关闭不必要的日志,使用异步日志框架,或者在填充完成后再统一记录结果。同时,确保在填充过程中捕获和处理异常,避免因个别错误导致整体任务失败。 最后,给出一个综合的解决方案示例,结合分页查询、并发填充、模板缓存和内存优化,帮助用户具体实施。还可以对比不同优化方案的效果,让用户根据自身情况选择合适的策略。 总结下来,我需要将问题原因和优化策略分点列出,每个策略下提供具体的实现方法和代码示例,同时结合用户之前的使用场景,给出针对性的建议,确保回答清晰、实用,能够有效解决用户的问题。</think>以下是针对EasyExcel填充数据缓慢问题的优化解决方案,包含8个关键优化方向及具体实施方法: --- ### 一、性能瓶颈分析 ```java // 典型问题代码示例 List<Data> allData = queryAllFromDB(); // 一次性加载全量数据 ExcelWriter writer = EasyExcel.write("output.xlsx").build(); WriteSheet sheet = EasyExcel.writerSheet().build(); writer.write(allData, sheet); // 同步写入 writer.finish(); ``` 常见性能问题: 1. 全量数据内存加载(内存溢出风险) 2. 同步单线程写入(CPU利用率低) 3. 复杂模板解析开销(样式/公式处理耗时) 4. 频繁GC导致卡顿 --- ### 二、核心优化方案 #### 1. 分页流式写入(内存优化) ```java int pageSize = 2000; int total = getTotalCount(); int pages = (total + pageSize - 1) / pageSize; ExcelWriter writer = EasyExcel.write("output.xlsx") .registerWriteHandler(new PageWriteHandler()) // 自定义分页处理器 .build(); for (int i = 0; i < pages; i++) { List<Data> pageData = queryByPage(i, pageSize); // 分页查询 writer.write(pageData, sheet); } writer.finish(); ``` #### 2. 异步并发填充(CPU优化) ```java ExecutorService executor = Executors.newFixedThreadPool(8); List<CompletableFuture<Void>> futures = new ArrayList<>(); int batchSize = 5000; List<List<Data>> chunks = ListUtils.partition(allData, batchSize); for (List<Data> chunk : chunks) { futures.add(CompletableFuture.runAsync(() -> { try (ExcelWriter writer = EasyExcel.write(new File("temp_" + UUID.randomUUID()))) { writer.write(chunk, sheet); } }, executor)); } CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); mergeTempFiles(); // 合并临时文件 ``` --- ### 三、模板优化策略 #### 1. 简化模板结构 - 移除不必要的合并单元格 - 用`{{.variable}}`替代复杂公式 - 禁用自动列宽计算 ```java ExcelWriter writer = EasyExcel.write(file) .withTemplate(template) .autoCloseStream(true) .needHead(false) // 关闭表头处理 .registerWriteHandler(new NoAutoWidthHandler()) // 自定义不自动调整列宽 .build(); ``` #### 2. 模板预编译缓存 ```java // 使
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值