记录解决了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次网络请求来判断是否包含字典缓存,所以为什么之前导出速度缓慢就可以理解了。

Logo

快速构建 Web 应用程序

更多推荐