Aop+自定义注解实现数据字典映射

数据字典

    Web项目开发中,字典表的一般都会存在,主要用来给整个系统提供基础服务。

  比如男女性别的类型可以使用0和1来进行表示,在存储数据和查询数据的时候,就可以使用字典表中的数据进行翻译处理。

   再比如之前做的一个项目中宠物类型包含老虎-1、海豚-2、大象-3、长颈鹿-4等等;做答题处理时的答题类型,比如单选题、多选题、填空题等等;

   比较常用的有用户类型,普通用户还是VIP用户等等;这些类型的数据也可以存储在数据字典中进行统一处理。

数据结构以及业务

举例:

在业务表中使用的是业务目录,而其中的“数据结构”,“数据目录类型”,“数据来源”使用的是字典表中的id,但我们在页面显示的时候,想用字典表中的value值,即我们在库里相应的业务表的外键存字典表的ID,查询的时候返回给前端在字典表中的value字段。

如以下表以及内容的展示:

目录表:

内容: 外键,字典的ID

如图所示:(字典表)需要我们在表中拿出外键所对应的Label

列举方法

   那有什么办法可以让我们查询出key的同时,将value值也查询出来,当时处理问题的时候想了三种解决方法:

1.要么就写sql 匹配。(在SQL查询的时候做字段的匹配,但是增加了SQL的复杂性不易维护)

2.要么就业务处理。得写个工具类(针对当时的情况感觉可以实现,然后查找了一些资料比如,自定义注解啥的)

3.再就是前端调两次接口,查询一次数据,再查询一次字典表做转译。(所涉及到的字段有四五个甚至更多,这是我问的别的同学,他们公司采用的就是这种方法)

根据查找资料以及实际考虑,最终采用了使用自定义注解的方式去做字段映射。

实现步骤:

首先展示项目的目录结构

总共三个,自定义注解,切面,具体实现

一 ,创建自定义注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Dict {/*** 字典类型,对应数据库的类型,下方展示*/String dictType();/*** 实体类内对应的中文字段名,默认为“当前字段+Text”* <p>*  例如当前字段为“type”,则对应中文字段默认为“typeText”* 就是你在实体中想要给某个字段赋值,这个字段就要写成那个字段*/String dictField() default "";}

二,创建切面


@Slf4j
@Aspect
@Component
public class BydDaoDictAspect {
//  * `@Pointcut` 注解用于定义一个切点,切点是决定哪些方法应该被通知(advice)的表达式。
//	* `execution(* com.wiseom.asset.manage.service..*.*(..))` 是一个切点表达式,它表示匹配 `com.wiseom.asset.manage.service` 包及其子包下的所有类的所有方法。
//	* `doPointcut()` 方法本身没有执行任何操作,它只是为切点提供了一个标识符。@Pointcut("execution(* com.wiseom.asset.manage.service..*.*(..))")public void doPointcut() {}
//  * `@AfterReturning` 注解表示在匹配的方法成功执行并返回结果后执行通知。
//  * `pointcut = "doPointcut()"` 表示这个通知应用于之前定义的 `doPointcut` 切点。
//	* `returning = "result"` 捕获返回的结果,并将其作为 `result` 参数传递给通知方法。
//	* 在 `doAfterReturning` 方法中,`DictUtils.convertDict(result)` 是将返回的 `result` 对象转换为字典格式。如果在转换过程中发生异常,它会被捕获并记录到日志中。@AfterReturning(pointcut = "doPointcut()", returning = "result")public void doAfterReturning(JoinPoint pjp, Object result) {try {DictUtils.convertDict(result);} catch (Exception e) {log.error(String.valueOf(e));}}
}

备注:

1:切点在com.wiseom.asset.manage.service的原因,因为我们使用了mybitsplus ,在我们处理查询,或者执行SQL的时候大多都在service层就被处理了,所以切点设在了service层。

2:如果切点设置在Controller,则需要修改下方实现三中的 方法 convertDict,因为 Controller的返回值拿到的是 R 包裹的内容,返回值对象是R,而不是实体,具体问题查看下方的扩展四

三,创建实现


@Component
public class DictUtils {private static final String DICT_FIELD_SUFFIX = "Text";/*** 统一获取当前实体类涉及到的字典表数据,避免多次查询数据库造成的性能消耗* 这个工具类主要的改动就是这个,所以我就把这一段放在上面了* @param dictNames 字典表type值* @return 字典表数据*/@SneakyThrowsprivate static  Map<String, Map<Long, String>> getDictMap(List<String> dictNames) {//创建fegin的实例,调用系统的sys_dict_item表,需要新写一个接口final 	RemoteDictService remoteDictService = SpringContextHolder.getBean(RemoteDictService.class);//把注解里的所有字典的类型查出来,具体根据需求做查询List<SysDictItem> dictList = remoteDictService.getDictItemByType(dictNames);//根据类型做一个分组,这里对应的是 Map<DictType, Map<ID, Label>>return dictList.stream().collect(Collectors.groupingBy(SysDictItem::getDictType, Collectors.toMap(SysDictItem::getId, SysDictItem::getLabel, (v1, v2) -> v2)));}/*** 这是主方法主要是判断AOP后返回的内容,并根据判断条件拿到里面的内容*  然后先判断有哪些带有注解,根据注解拿到对应的dictType去库里查找对应的内容 *  根据拿到内容在对指定的字段dictField进行赋值*/public static void convertDict(Object target) {//判断对象是不是分页的if (target instanceof Page) {//拿到分页中的records对象里边包含实体的内容for(Object object : ((Page<?>)target).getRecords()){//拿到实体List<DictDefinition> dictDefinitions = getMetadata(object);// 如果没有注解,则直接返回if (CollectionUtils.isEmpty(dictDefinitions)) return;// 从字典定义中提取所有的注解的 dictType(数据库字典的类型)List<String> dictNames = dictDefinitions.stream().map(d -> d.getDict().dictType()).collect(Collectors.toList());// 根据字典类型获取字典映射Map<String, Map<Long, String>> dictMapMap = getDictMap(dictNames);// 转换target对象的字典字段doConvertDict(object, dictDefinitions, dictMapMap);}} elseif (target instanceof List) {// 将target强制转换为List<?>类型List<?> objectList = ((List<?>) target);// 使用CollectionUtils.isNotEmpty来检查列表是否非空if (CollectionUtils.isNotEmpty(objectList)) {// 获取列表中第一个元素的字典定义List<DictDefinition> dictDefinitions = getMetadata(objectList.get(0));// 如果没有注解,则直接返回if (CollectionUtils.isEmpty(dictDefinitions)) return;// 从字典定义中提取所有的注解的 dictType(数据库字典的类型)List<String> dictNames = dictDefinitions.stream().map(d -> d.getDict().dictType()).collect(Collectors.toList());// 根据字典类型获取字典映射Map<String, Map<Long, String>> dictMapMap = getDictMap(dictNames);// 遍历列表中的每个元素,并转换其字典字段objectList.forEach(t -> doConvertDict(t, dictDefinitions, dictMapMap));}} else {// 如果target不是List,则直接获取其字典定义List<DictDefinition> dictDefinitions = getMetadata(target);// 如果没有注解,则直接返回if (CollectionUtils.isEmpty(dictDefinitions)) return;// 从字典定义中提取所有的注解的 dictType(数据库字典的类型)List<String> dictNames = dictDefinitions.stream().map(d -> d.getDict().dictType()).collect(Collectors.toList());// 根据字典类型获取字典映射Map<String, Map<Long, String>> dictMapMap = getDictMap(dictNames);// 转换target对象的字典字段doConvertDict(target, dictDefinitions, dictMapMap);}}/*** 仅获取一次Dict元数据,降低多次反射造成的性能消耗* @param target 目标实体类* @return Dict元数据*/private static List<DictDefinition> getMetadata(Object target) {//这一段是判断存在注解的字段List<DictDefinition> dictDefinitions = new ArrayList<>();if (ClassUtils.isPrimitiveOrWrapper(target.getClass())|| target instanceof Map || target instanceof String) {return dictDefinitions;}List<Field> fields = FieldUtils.getAllFieldsList(target.getClass());for (Field field : fields) {Dict dict = AnnotationUtils.getAnnotation(field, Dict.class);if (dict != null) {DictDefinition dictDefinition = new DictDefinition();dictDefinition.setDict(dict);dictDefinition.setField(field);dictDefinitions.add(dictDefinition);}}return dictDefinitions;}@SneakyThrowsprivate static void doConvertDict(Object target, List<DictDefinition> dictDefinitions,Map<String, Map<Long, String>> dictMapMap) {for (DictDefinition dictDefinition : dictDefinitions) {//获取Dict注解和字段信息Dict dict = dictDefinition.getDict();Field field = dictDefinition.getField();//获取字典映射Map<Long, String> dictMap = dictMapMap.get(dict.dictType());//读取实体中带有注解的字段原有的值String dictCode = String.valueOf(FieldUtils.readField(target, field.getName(), true));//类型转换,我在库中用的存储的字段是String,所以需要进行类型的转换,如果你的库设置的不是,或者其他的类型,需要注意到这个地方Long longDictCode=Long.valueOf(dictCode);//拿到字典注解的labelString dictField = StringUtils.isEmpty(dict.dictField()) ? field.getName() + DICT_FIELD_SUFFIX : dict.dictField();//设置字段的字典文本值FieldUtils.writeField(target, dictField, dictMap.get(longDictCode), true);}}@Datapublic static class DictDefinition {private Dict dict;private Field field;}}

系统表的查询接口:

//这个我是在SysDictController下添加的接口,目的是实现查找多个类型的内容,同时还需要写个fegin接口以供别的模块使用
/*** 通过字典类型查找字典* @param types 类型* @return 同类型字典*/@PostMapping("/types")public List<SysDictItem> getDictByTypes(@RequestBody List<String> types) {return sysDictItemService.list(Wrappers.<SysDictItem>query().lambda().in(SysDictItem::getDictType, types));}

使用方法

直接在实体字段上方添加注解

1.dictType:数据库中的字典的类型

2.dictField:实体中赋值的字段

如上方使用方法所示,如果你想将字典中的label值给实体中的 catalogStructure,则标注

如果前端需要,一个原来的ID,一个label,那就需要创建一个DTO,如图所示,将dictField 后边写上你想要的字段名称就行了

四,备注以及扩展

这个是公司用的pigx框架,没用到的同学,可以跳过了,仅供参考,依据具体项目实际来定

参数的入值:

@AfterReturning(pointcut = "doPointcut()", returning = "result")public void doAfterReturning(JoinPoint pjp, Object result) {try {DictUtils.convertDict(result);} catch (Exception e) {log.error(String.valueOf(e));}}

这个代码中的result需要着重注意,他可能会存在判断的问题

举例:这个是正常的,只拿到这个返回的实体就行,在我们实现三中的方法convertDict所有if()的目的都是为了拿到返回值中的实体

比如底下这个图,分页返回的就需要做一个判断(实现三我已经加上了,这个做一个参考):

判断:

五:具体实现 优化1,多个字段的匹配,用“,”隔开代码优化


@Component
public class DictUtils {private static final String DICT_FIELD_SUFFIX = "Text";/*** 执行字典转换的公共方法* @param target 待转换的对象*/public static void convertDict(Object target) {//判断是否是分页if (target instanceof Page) {for (Object object : ((Page<?>) target).getRecords()) {convertDictInternal(object);}//是否是list} else if (target instanceof List) {if (CollectionUtils.isNotEmpty((List<?>) target)) {for (Object object : (List<?>) target) {convertDictInternal(object);}}} else {convertDictInternal(target);}}/*** 内部的字典转换实现方法* @param target 待转换的对象*/@SneakyThrowsprivate static void convertDictInternal(Object target) {// 获取目标对象的字典定义List<DictDefinition> dictDefinitions = getMetadata(target);// 如果没有字典定义则直接返回if (CollectionUtils.isEmpty(dictDefinitions)) {return;}// 提取字典类型并获取字典映射String commaSeparatedString = dictDefinitions.stream().map(d -> d.getDict().dictType()).collect(Collectors.joining(","));List<String> dictNames = Arrays.asList(commaSeparatedString.split(","));Map<String, Map<Long, String>> dictMapMap = getDictMap(dictNames);// 执行具体的字典转换操作doConvertDict(target, dictDefinitions, dictMapMap);}/*** 获取目标对象的字典定义* @param target 目标对象* @return 字典定义列表*/private  static List<DictDefinition> getMetadata(Object target) {List<DictDefinition> dictDefinitions = new ArrayList<>();// 排除基本类型、包装类型、Map 和 String 类型if (ClassUtils.isPrimitiveOrWrapper(target.getClass())|| target instanceof Map || target instanceof String) {return dictDefinitions;}// 获取目标对象的所有字段List<Field> fields = FieldUtils.getAllFieldsList(target.getClass());for (Field field : fields) {// 获取字段上的 Dict 注解Dict dict = AnnotationUtils.getAnnotation(field, Dict.class);if (dict!= null) {// 创建并添加字典定义DictDefinition dictDefinition = new DictDefinition();dictDefinition.setDict(dict);dictDefinition.setField(field);dictDefinitions.add(dictDefinition);}}return dictDefinitions;}/*** 根据字典名称获取字典映射* @param dictNames 字典名称列表* @return 字典映射*/@SneakyThrowsprivate static  Map<String, Map<Long, String>> getDictMap(List<String> dictNames) {final RemoteDictService remoteDictService = SpringContextHolder.getBean(RemoteDictService.class);List<SysDictItem> dictList = remoteDictService.getDictItemByType(dictNames);return new HashMap<>(dictList.stream().collect(Collectors.groupingBy(SysDictItem::getDictType, Collectors.toMap(SysDictItem::getId, SysDictItem::getLabel, (v1, v2) -> v2))));}/*** 执行具体的字典字段转换* @param target 目标对象* @param dictDefinitions 字典定义列表* @param dictMapMap 字典映射*/@SneakyThrowsprivate static  void doConvertDict(Object target, List<DictDefinition> dictDefinitions,Map<String, Map<Long, String>> dictMapMap) {for (DictDefinition dictDefinition : dictDefinitions) {Dict dict = dictDefinition.getDict();Field field = dictDefinition.getField();String[] dictTypes = dict.dictType().split(",");for (String dictType : dictTypes) {Map<Long, String> dictMap = dictMapMap.get(dictType.trim());// 读取字段的字典编码String dictCode = String.valueOf(FieldUtils.readField(target, field.getName(), true));// 类型转换Long longDictCode = Long.valueOf(dictCode);// 确定字典字段名String dictField = StringUtils.isEmpty(dict.dictField())? field.getName() + DICT_FIELD_SUFFIX : dict.dictField();// 设置字段的字典文本值if (dictMap!= null && dictMap.containsKey(longDictCode)) {FieldUtils.writeField(target, dictField, dictMap.get(longDictCode), true);}}}}@Datapublic static class DictDefinition {private Dict dict;private Field field;}
}

使用方式:

参考:

主要内容参考:

java自定义注解实现数据字典映射_字典映射 java-CSDN博客

分页扩展参考:

字典翻译@Dict - 莫大人 - 博客园

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/466399.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cursor的chat与composer的使用体验分享

经过一段时间的试用&#xff0c;下面对 Composer 与 Chat 的使用差别进行总结&#xff1a; 一、长文本及程序文件处理方面 Composer 在处理长文本时表现较为稳定&#xff0c;可以对长文进行更改而不会出现内容丢失的情况。而 Chat 在更改长的程序文件时&#xff0c;有时会删除…

小北的字节跳动青训营与调用模型:调用模型:OpenAI API vs 微调开源Llama2/ChatGLM(持续更新中~~~)

前言 最近&#xff0c;字节跳动的青训营再次扬帆起航&#xff0c;作为第二次参与其中的小北&#xff0c;深感荣幸能借此机会为那些尚未了解青训营的友友们带来一些详细介绍。青训营不仅是一个技术学习与成长的摇篮&#xff0c;更是一个连接未来与梦想的桥梁~ 小北的青训营 X M…

Axure设计之三级联动选择器教程(中继器)

使用Axure设计三级联动选择器&#xff08;如省市区选择器&#xff09;时&#xff0c;可以利用中继器的数据存储和动态交互功能来实现。下面介绍中继器三级联动选择器设计的教程&#xff1a; 一、效果展示&#xff1a; 1、在三级联动选择器中&#xff0c;首先选择省份&#xff…

七次课掌握 Photoshop:选区与抠图

Photoshop 是一门选择的艺术。Photoshop 提供了多种工具和方法来创建选区&#xff0c;适用于不同的场景和需求。 理解和熟练使用这些工具&#xff0c;是提高图像处理能力的关键。 ◆ ◆ ◆ 选区方法与操作 一、创建选区的工具和命令 1、选区工具 &#xff08;1&#xff09;选…

智慧商城项目-VUE2

实现效果 项目收获 通过本项目的练习&#xff0c;可以掌握以下内容&#xff1a; 创建项目 ##基本创建 基于 VueCli 自定义创建项目架子,并对相关的配置进行选择 vue create demo-shopping调整目录 删除文件 删除初始化的一些默认文件 src/assets/logo.pngsrc/components…

Java | Leetcode Java题解之第546题移除盒子

题目&#xff1a; 题解&#xff1a; class Solution {int[][][] dp;public int removeBoxes(int[] boxes) {int length boxes.length;dp new int[length][length][length];return calculatePoints(boxes, 0, length - 1, 0);}public int calculatePoints(int[] boxes, int l…

PDF多功能工具箱 PDF Shaper v14.6

如今对PDF处理的软件很多都是只是单一的功能。PDF Shaper给你完全不同的体验&#xff0c;因为PDF Shaper是一款免费的PDF工具集合的软件。有了PDF Shaper&#xff0c;你以后再也不用下载其他处理PDF的软件了。PDF Shaper的功能有&#xff1a;合并&#xff0c;分割&#xff0c;加…

论文阅读--捍卫基于激光雷达视野范围的三维目标检测

目前存在的问题&#xff1a; 常用的体素化或鸟瞰图&#xff08;BEV&#xff09;表示相比&#xff0c;范围视图表示更紧凑且没有量化误差&#xff0c;但其在目标检测方面的性能很大程度上落后于体素化或 BEV 。范围视图尺度变化的挑战2D 图像不同&#xff0c;虽然距离图像的卷积…

使用匿名管道时出现程序一直运行问题

父进程创建两个子进程&#xff0c;父子进程之间利用管道进行通信。要求能显示父进程、子进程各自的信息&#xff0c;体现通信效果。(源程序pipe_1.c) 一开始&#xff0c;我忘了初始化pipe,很傻*的直接把fd当管道使&#xff0c;出现了儿子喊爸爸"i am your father."的…

【笔记】开关电源变压器设计 - 工作磁通的选择原则

变压器设计中有一个重要的输入参数&#xff0c;是选定电路工作的磁路参数。涉及到磁场的上下震荡最高幅度。如上图所示。磁场的方向有正负&#xff0c;所以如果电流在越过零点震荡&#xff0c;只考虑半周来和Bs或者Bmax比对即可。Bs,Bmax与特定材料有关。材料给出的最大Bmax,或…

一文了解Android本地广播

在 Android 开发中&#xff0c;本地广播&#xff08;Local Broadcast&#xff09;是一种轻量级的通信机制&#xff0c;主要用于在同一应用进程内的不同组件之间传递消息&#xff0c;而无需通过系统的全局广播机制。这种方法既可以提高安全性&#xff08;因为广播仅在应用内传播…

Spark本地模式安装

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 大数据与数据分析_夏天又到了的博客-CSDN博客 Hadoop完全分布式环境搭建步骤-CSDN博客,前置环境安装参看此博文 Spark本地模式安装 Spark本地模式的安…

SpringSecurity6+OAuth2.0 从入门到熟练使用

文章目录 简介1、快速入门1.1 准备工作我们先要搭建一个SpringBoot工程① 创建工程 添加依赖② 创建启动类③ 创建Controller1.2 引入SpringSecurity2、 认证2.1 登录校验流程2.2 原理分析2.2.1 SpringSecurity完整流程2.2.2 认证流程详解概念速查:2.3 解决问题2.3.1 思路分析2…

计算机网络——SDN

分布式控制路由 集中式控制路由

自动驾驶革命:从特斯拉到百度,谁将主宰未来交通?

内容概要 自动驾驶技术正在经历一个前所未有的革命性变化&#xff0c;各大企业纷纷抢占这一充满潜力的新市场。以特斯拉和百度为代表的行业巨头&#xff0c;正利用各自的优势在这一技术的赛道上展开激烈竞争。特斯拉凭借其在电动汽车和自动驾驶领域的前瞻性设计与不断革新的技…

Group By、Having用法总结(常见踩雷点总结—SQL)

Group By、Having用法总结 目录 Group By、Having用法总结一、 GROUP BY 用法二、 HAVING 用法三、 GROUP BY 和 HAVING 的常见踩雷点3.1 GROUP BY 选择的列必须出现在 SELECT 中&#xff08;&#x1f923;最重要的一点&#xff09;3.2 HAVING 与 WHERE 的区别3.3 GROUP BY 可以…

MySQL存储目录与配置文件(ubunto下)

mysql的配置文件&#xff1a; 在这个目录下&#xff0c;直接cd /etc/mysql/mysql.conf.d mysql的储存目录&#xff1a; /var/lib/mysql Ubuntu版本号&#xff1a;

深度学习经典模型之Network in Network

1 Network in Network 1.1 模型介绍 ​ Network In Network (NIN)是由 M i n L i n Min Lin MinLin等人提出&#xff0c;在CIFAR-10和CIFAR-100分类任务中达到当时的最好水平&#xff0c;因其网络结构是由三个多层感知机堆叠而被成为NIN [ 5 ] ^{[5]} [5]。NIN以一种全新的角…

Java版ERP管理系统源码解析:利用Spring Cloud Alibaba和Spring Boot实现微服务架构

ERP系统&#xff0c;亦称为企业资源计划系统&#xff0c;是一种融合了企业多元部门和复杂业务的综合管理信息系统。在全球经济蓬勃发展及企业竞争日趋激烈的背景下&#xff0c;ERP系统已逐步跃升为现代企业管理的核心工具。该系统通过优化资源配置及提升业务流程效率&#xff0…

Python 基础笔记之生成器generator

生成斐波拉契数列 def fib(length):a,b0,1n0while n<length:yield aa,bb,abn1return abc g2fib(10) try:print(next(g2)) 生成器方法&#xff1a; __next__():获取下一个元素 send(value):向每次生成器调用中传值 注意&#xff1a;第一次调用send(None) def gen():i0while…