EasyExcel合并行处理并优化
业务场景
由于业务需要导出如下图中订单数据和订单项信息,而一个订单对应多个订单项,所以会涉及到自定义合并行
1.简单处理
项目使用的EasyExcel,经查找发现Excel种有个AbstractMergeStrategy抽象类,可以用于合并单元格。
于是先简单的写一个工具类实现根据数据自定义合并单元行,基本思路是获取当前单元格内容和上一行的单元格内容比对,如果相同则添加合并区域,如果不同则不处理,如果上一个单元格在合并区域中,则先移除合并区域再将当前单元格添加到合并区域中。
2.优化数据显示
第一个版本上线后财务反馈数据有问题。如图,求和数值实际应该是45,但是显示为90,导致财务不好对账
z
经排查发现图中C3、C5单元格虽然已经合并了,但是数据仍然存在,导致下拉选中的时候将他们的值也计算进去了。于是在1.0的基础上调整,将合并单元格仅保留首行数据,其他行内容直接清空
3.优化合并策略
第二版上线后数据问题已经解决,但是财务反馈导出大量数据时太慢。本来打算调整为异步导出解决此问题,但是经过测试发现10000条数据如果不合并直接生成excel只要几秒,但是使用合并自定义合并策略就非常慢,需要一二十分钟,这个时间差大的太离谱了,于是查看合并策略代码有哪些地方可以优化的。
经过查看代码可以发现在上述2.1步骤中一直查找合并单元格数据,然后一直删除再新增。假设10000条数据,有3列需要自动合并,每三行合并,执行以上代码会执行6666✖️3次新增和3333✖️3次删除,大大的影响了效率。于是调整代码,处理数据时只保存需要合并的单元格信息,导出完成再统一添加合并信息到sheet
经过测试,原本生成文件需要20分钟左右,现优化到只需要20多秒了。
工具类
/**
* excel线程上下文.
*
*/
public class ExcelThreadContext {
private static final ThreadLocal<Map<String, Object>> THREAD_LOCAL = ThreadLocal.withInitial(HashMap::new);
public static void clear() {
THREAD_LOCAL.remove();
}
public static void setData(String key, Object value) {
Map<String, Object> map = get();
map.put(key, value);
}
public static Integer getInteger(String key) {
return getInteger(key, 1);
}
public static Integer getInteger(String key, Integer defaultValue) {
Map<String, Object> map = get();
return Convert.toInt(map.get(key), defaultValue);
}
public static <K, V> Map<K, V> getMap(String key) {
return getMap(key, new HashMap<>());
}
public static <K, V> Map<K, V> getMap(String key, Map<K, V> defaultValue) {
Map<String, Object> map = get();
try {
return (Map<K, V>) map.getOrDefault(key, defaultValue);
} catch (Exception e) {
return defaultValue;
}
}
public static <T> T getObject(String key) {
return getObject(key, null);
}
public static <T> T getObject(String key, T defaultValue) {
Map<String, Object> map = get();
try {
return (T) map.getOrDefault(key, defaultValue);
} catch (Exception e) {
return defaultValue;
}
}
private static void set(Map<String, Object> map) {
THREAD_LOCAL.set(map);
}
public static Map<String, Object> get() {
return THREAD_LOCAL.get();
}
}
因为数据是存储在线程中的,需要每次使用后清理线程数据