记录解决了excel导出速度过慢的问题。以前7-8k行的数据,在导出的时候,需要7-8min左右,这显然不能达到想要的效果。经过多种尝试,查阅N多资料。最终对该问题进行了解决,以下是解决方案。
在git上ruoyi的项目提交记录中,我们找到了如下文件在20230404的提交记录:
类路径:ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
说明:导出Excel,@Excel注解使用dictType属性时,如果有大量的字典数据,就会有大量的查询redis(打开、关闭),导致特别慢。于是使用map存储字典数据,相同的key就不需要再次去查询redis,大大提高了导出效率。
通过上述内容我们可以猜测,应该是项目中之前的代码,在导出excel数据的时候,如果有大量字典数据,就会频繁的请求redis数据,大量的网络请求,导致导出进度变得非常缓慢,因此改用HashMap对字典数据的kv值进行了本地存储,将网络请求数据,改成了从内存中读取数据。经过实际测试,导出的效率大大提升。原来需要几分钟导出的数据,现在只需要5s内即可完成,成功达成我们的要求。接下来对代码进行深度分析。
我们看到,此次的提交记录,对如下代码进行了更改:
首先声明了一个全局Map:
/**
* 导出Excel时,如果有大量的字典数据,就会有大量的查询redis(打开、关闭),导致特别慢。
* 于是使用map存储字典数据,相同的key就不需要再次去查询redis
*/
public Map<String,String> sysDictMap = new HashMap<String,String>();
对addcell方法中的代码进行了修改:
else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value))
{
cell.setCellValue(convertDictByExp(Convert.toStr(value), dictType, separator));//移除的代码
if (!sysDictMap.containsKey(dictType+value)){
String lable = convertDictByExp(Convert.toStr(value), dictType, separator);
sysDictMap.put(dictType+value,lable);
}
cell.setCellValue(sysDictMap.get(dictType+value));
}
观察代码我们可以看到,这个方法是建立一个excel单元格的方法,通过反射拿到当前行当前列的column和value,判断excel注解中,如果标注了dictType属性,则表明是字典数据。首先会判断sysDictMap中是否包含dictType+value的key,如果有则直接获取,如果没有就会去请求数据。
我们分析以下请求字典数据的这个方法:convertDictByExp
/**
* 解析字典值
*
* @param dictValue 字典值
* @param dictType 字典类型
* @param separator 分隔符
* @return 字典标签
*/
public static String convertDictByExp(String dictValue, String dictType, String separator)
{
return DictUtils.getDictLabel(dictType, dictValue, separator);
}
再进一步
/**
* 根据字典类型和字典值获取字典标签
*
* @param dictType 字典类型
* @param dictValue 字典值
* @param separator 分隔符
* @return 字典标签
*/
public static String getDictLabel(String dictType, String dictValue, String separator)
{
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType);//此处会请求redis缓存数据
if (StringUtils.isNotNull(datas))
{
if (StringUtils.containsAny(separator, dictValue))
{
for (SysDictData dict : datas)
{
for (String value : dictValue.split(separator))
{
if (value.equals(dict.getDictValue()))
{
propertyString.append(dict.getDictLabel()).append(separator);
break;
}
}
}
}
else
{
for (SysDictData dict : datas)
{
if (dictValue.equals(dict.getDictValue()))
{
return dict.getDictLabel();
}
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
在上方注释处,//此处会请求redis缓存数据,点进去我们继续查看getDictCache方法
/**
* 获取字典缓存
*
* @param key 参数键
* @return dictDatas 字典数据列表
*/
public static List<SysDictData> getDictCache(String key)
{
JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
if (StringUtils.isNotNull(arrayCache))
{
return arrayCache.toList(SysDictData.class);
}
return null;
}
我们再点击getCacheObject方法查看到底是如何获取缓存的
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
到这里,我们可以看到,在上述方法中,获取了一个springdataredis的操作对象,通过这个对象去操作查看redis是否有对应的缓存,是网络请求。我们不妨做一个分析,假设有1w行数据,每行有5个字典数据,那按照上面的代码逻辑,就需要进行5w次网络请求来判断是否包含字典缓存,所以为什么之前导出速度缓慢就可以理解了。