导出列表
导出列表的时候,我发现实体类中的性别是存放的字典表的数值,无法转换为字符串,但是客户要求必须显示为男或者女,
@ExcelProperty("性别")
@Schema(description = "性别")
private Integer sex;
我想了好几种办法,最笨的办法就是对查询出来的列表进行遍历,然后把对应的字段值取出来,去数据库中查询对应的字符串,这种方式特别繁琐,而且一个实体类中,可能引用了多种字典表的值,这样就需要挨个去查询数据库,特别繁琐,我有20个表需要导出,我得话费一整天的时间去写导出,写完的导出功能,在性能方面也很慢。数据库大的话导出的时候需要花费几十秒甚至是分钟级别。
二,我也想到从缓存中取值,但是发现缓存中存放的字典值是这种格式
也就是把acceptManage类型字典数据全部放到一个value中,我如果从缓存中拿,我需要根据key查询到对应的value,再对value进行处理,value里面是一个集合,我需要对集合中的字典对象遍历,根据sex的数值,去这个对象中拿到对应的字符串值,这样处理起来也是稍微麻烦,而且性能方面,也会很慢,因为acceptManage这个key中,对应的value,可能包含100个字典对象,我导出的没一条数据,都需要在这100个对象中取遍历取值,我导出100条数据,就需要遍历1万次,效率也是无法提高。
三:我突然看到easyExcel中的注解@ExcelProperty("性别"),心想easyExcel中有没有注解可以对字典表中的值进行转换,查询了一圈,只看有个转换器可以用,但是每一个字段的转换,我都需要创建一个转换器来使用,还是麻烦。
愁死人了
四:我睡了一觉之后,我想到了反射,因为@ExcelProperty这个注解也是基于反射实现的,我也可以,因此我自定义了一个注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DictLabel {
/**
* 字典类型
* @return
*/
String dictType();
}
接下来是对注解的处理
/**
* 用于处理字典值转换
*/
@Component
public class DictLabelAspect {
private SysDictItemService sysDictItemService;
private HashMap<String, String> dictItemHashMap = new HashMap<>();
public DictLabelAspect(SysDictItemService sysDictItemService){
this.sysDictItemService = sysDictItemService;
constructorDictItem();
}
/**
* 初始化字段表中的数据到本地缓存中
* @return
*/
public String constructorDictItem() {
// 从数据库中获取字典项数据
List<SysDictItem> dictList = sysDictItemService.list();
// 使用Stream流处理数据并初始化字典项HashMap
dictList.stream()
.forEach(sysDictItem -> {
String itemValue = sysDictItem.getItemValue();
String dictType = sysDictItem.getDictType();
String label = sysDictItem.getLabel();
dictItemHashMap.put(dictType + ":" + itemValue, label);
});
// 返回初始化完成提示信息
return "字典表数据初始化完成!";
}
/**
* 为对象的带有@DictLabel注解的字段应用字典标签
*
* @param obj 要处理的对象
* @return 处理后的对象
*/
public Object applyDictLabels(Object obj) {
if (obj == null) {
return obj;
}
// 用于缓存字典项的哈希映射
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
try {
for (Field field : fields) {
field.setAccessible(true);
// 检查字段是否带有@DictLabel注解
if (!field.isAnnotationPresent(DictLabel.class)) {
continue;
}
Object value = field.get(obj);
if (value == null) {
continue;
}
DictLabel annotation = field.getAnnotation(DictLabel.class);
String dictKey = annotation.dictType() + ":" + value.toString();
if (dictItemHashMap.containsKey(dictKey)) {
// 如果缓存中存在对应的字典项值,则直接使用缓存值
String dictLabelValue = dictItemHashMap.get(dictKey);
field.set(obj, dictLabelValue);
} else {
// 否则查询数据库获取字典项值
String dictLabelValue = sysDictItemService.findByDictTypeAndItemValue(annotation.dictType(), value.toString());
if (dictLabelValue != null) {
// 将查询结果加入缓存并设置字段值
dictItemHashMap.put(dictKey, dictLabelValue);
field.set(obj, dictLabelValue);
}
}
}
} catch (IllegalAccessException e) {
// 处理反射访问权限异常
e.printStackTrace();
}
// 恢复字段访问权限
for (Field field : fields) {
field.setAccessible(false);
}
return obj;
}
}
处理逻辑是:先根据反射,读取这个类上的字段是否加上了DictLabel注解,没有加上的去下一个字段查找。
找到之后取出来DictLabel注解里面对应的dictType值,这个就是字段表里的值
/**
* 性别
*/
@DictLabel(dictType = "gender")
@ExcelProperty("性别")
private String gender;
这个sex的值就是字典表中的item_value值,我要得到对应的lable值
查出来这俩值后,去数据库查询对应的lable值
@Override
public String findByDictTypeAndItemValue (String dictType , String itemValue) {
QueryWrapper< SysDictItem > sysDictItemQueryWrapper = new QueryWrapper<>();
sysDictItemQueryWrapper.eq("item_value",itemValue);
sysDictItemQueryWrapper.eq("dict_type",dictType);
return this.getOne(sysDictItemQueryWrapper).getLabel();
}
之后把这个值设置到sex中
field.set(obj, dictLabelValue);
完成之后,发现代码写起来不多,但是导出性能还是差。
缓存不能用,直接数据查询也不行。
只要在本地自己建一个缓存,于是自己创建了一个hashMap,用来把数据库查到的值直接放入到缓存中,这样下次导出的时候就不会查询数据库了,后来一想,用户在导出的时候,谁会进行重复导出了,一般只导出一遍
接下来我就想到能不能本地缓存预热,项目在启动的时候,就把字典表中的数据全部放入到缓存中,这样导出的时候,直接再本地内存操作,不需要走数据库了。
因此我在构造函数里,直接先查出来,并放入缓存。这时候,导出的时候的性能大幅度提升
这是接口
@ResponseExcel
@GetMapping("/export")
@PreAuthorize("@pms.hasPermission('cbResident_cbResident_export')")
public List<WxCbResidentExcel> export(WxCbResidentEntity wxCbResident, Long[] ids) {
List< WxCbResidentEntity > list = wxCbResidentService.list(Wrappers.lambdaQuery(wxCbResident).in(ArrayUtil.isNotEmpty(ids) , WxCbResidentEntity::getId , ids));
// 使用 Stream API 简化代码,并显式指定泛型类型
List< WxCbResidentExcel > WxCbResidentExcels = BeanUtil.copyToList(list , WxCbResidentExcel.class);
List< WxCbResidentExcel > residentList = WxCbResidentExcels.stream()
.map((wx) -> (WxCbResidentExcel) dictLabelAspect.applyDictLabels(wx))
.collect(Collectors.toList());
return residentList;
}
这个接口中,先会查询出来对应的数据,接下来需要转换为excel的实体类,为什么要转换呢?
因为实体类中,有的人会把sex的类型写成Integer类型,这样的话,在反射过程中,是无法给sex字段赋值的。所以再excel的实体类中,所有的字段的整型类型应该全部写为String,这样在导出的时候反射可以实现。
我知道可以把自己写的注解@DictLabel(dictType = "gender")按照@ExcelProperty("性别")的处理方式来处理,但是我不会找他的源码来查看,我这个注解是通过方法调用来实现,我觉得可以按照aop代理的方式,但是时间来不及了,得先实现功能了,后序有时间了再优化吧
代码逻辑可能写的不好,欢迎大家指导,谢谢