当前位置: 首页 > news >正文

数据绑定注解功能

数据绑定注解
在实际开发中会遇到code转name的情况,一般情况下的写法就是先把数据查出来,然后再把数据中code对应的名称查出来,最后组装成需要的数据返回给前端展示,这种情况是没与问题的。但是无形之中增加了一些不必要代码显的有些臃肿,如果是在访问量比较高的接口还会影响一些性能。今天就记录一下利用缓存cache,反射写的一个数据绑定的注解,无需关注code转name的过程,仅需写出主要代码就可以了,剩下的用注解操作。非常方便,快捷。废话不多说,直接上案例:
在这里插入图片描述
在这里插入图片描述
可以看到,数据绑定相同条件下第二次请求直接走缓存不查库,可以提高效率。
接下来上代码:
注解定义:

package com.example.test1.databind.aspect.annotation;import com.example.test1.databind.property.BaseFastDataBindProperty;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @Author xx* @Date 2024/6/28 9:11* @Description: 字段数据快速绑定* @Version 1.0*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FastDataBindFiled {/*** 指定数据绑定配置类*/Class<? extends BaseFastDataBindProperty> config();/*** 对应数据库表查询字段*/String conditionColumn() default "";/*** 条件字段例:要查字zt = 3的名称 这里传入zt*/String conditionProperty() default "";/*** 获取结果值的表字段*/String valueColumn() default "";/*** sql中拼接 and的语句*/String andCondition() default "";
}
package com.example.test1.databind.aspect.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @Author xx* @Date 2024/6/28 9:08* @Description: 快速数据绑定方法切入* @Version 1.0*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FastDataBindResult {/*** 深度查询次数默认1*/int deepQueryTimes() default 1;
}

注解生效的切面类:

package com.example.test1.databind.aspect;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.test1.databind.aspect.annotation.FastDataBindFiled;
import com.example.test1.databind.aspect.annotation.FastDataBindResult;
import com.example.test1.databind.bind.FastDataBind;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;/*** @Author xx* @Date 2024/6/28 9:02* @Description: 快速数据绑定切面类* @Version 1.0*/
@Aspect
@Component
public class DataBindAspect {@Around(value = "@annotation(fastDataBindResult)")public Object handle(ProceedingJoinPoint joinPoint, FastDataBindResult fastDataBindResult) {Object result = null;try {result = joinPoint.proceed();//探测次数for (int i = 0; i < fastDataBindResult.deepQueryTimes(); i++) {result = handleDataBindResult(result);}} catch (Throwable e) {throw new RuntimeException(e.getMessage(), e);}return result;}@Around(value = "@annotation(fastDataBindFiled)")public Object handle(ProceedingJoinPoint joinPoint, FastDataBindFiled fastDataBindFiled) {return handleDataBindResult(joinPoint);}private Object handleDataBindResult(Object data) {Object dataBindData;if (data instanceof Page) {Page<?> page = (Page<?>) data;dataBindData = page.getRecords();} else {dataBindData = data;}FastDataBind.dataBind(dataBindData);return data;}
}

具体实现逻辑

package com.example.test1.databind.bind;import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableName;
import com.example.test1.databind.aspect.annotation.FastDataBindFiled;
import com.example.test1.databind.property.BaseFastDataBindProperty;
import com.example.test1.databind.property.FastDataBindProperty;
import com.example.test1.databind.sql.SelectSqlBuilder;
import com.example.test1.util.SpElUtils;
import com.google.common.base.Joiner;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;import static cn.hutool.core.text.CharPool.COLON;
import static com.baomidou.mybatisplus.core.enums.SqlKeyword.AND;
import static com.baomidou.mybatisplus.core.toolkit.StringPool.DASH;
import static com.baomidou.mybatisplus.core.toolkit.StringPool.SINGLE_QUOTE;/*** @Author xx* @Date 2024/6/28 10:17* @Description: 快速数据绑定* @Version 1.0*/
@Slf4j
@Component
public class FastDataBind {/*** map<类,类中数据绑定的字段>*/private static final Map<Class<?>, Field[]> CLASS_DATABIND_FIELD_MAP = new ConcurrentHashMap<>(256);private static final Joiner COLON_JOINER = Joiner.on(COLON);public static final String LOGIN_TENANT_SHARED_TYPE = "loginTenantId";public static final String LOGIN_USER_SHARED_TYPE = "loginUserId";/*** 本地缓存,重启服务时会清空*/private static final Cache<String, DataBindCollector> CACHE = CacheBuilder.newBuilder()//过期时间.expireAfterAccess(10L, TimeUnit.SECONDS)//缓存容量大小.initialCapacity(128).build();/*** 数据绑定** @param data 目标属性带有{@link com.example.test1.databind.aspect.annotation.FastDataBindFiled}注解,将做数据绑定*/public static void dataBind(Object data) {if (data == null) {return;}Map<String, List<DataBindCollector>> collGroup;StopWatch stopWatch = new StopWatch();stopWatch.start();Field[] fields;if (data instanceof Collection) {Collection<?> list = (Collection<?>) data;if (CollUtil.isEmpty(list)) {return;}//查找出带有数据绑定注解的字段fields = list.stream().findFirst().map(FastDataBind::detectFields).orElse(null);if(fields == null){return;}//构建并为收集器做分组collGroup = list.parallelStream().flatMap(tmp -> buildCollectors(tmp).stream()).collect(Collectors.toList()).stream().collect(Collectors.groupingBy(DataBindCollector::groupKey));} else {fields = detectFields(data);collGroup = buildCollectors(data).stream().collect(Collectors.groupingBy(DataBindCollector::groupKey));}if (CollUtil.isEmpty(collGroup)) {return;}//根据分组数据做查询绑定dataBindGroup(collGroup);stopWatch.stop();log.info("======================数据绑定共耗时:{} ms===========================", stopWatch.getTotalTimeMillis());}/*** 根据数据绑定收集器进行数据绑定*/private static void dataBindGroup(Map<String, List<DataBindCollector>> collGroup) {for (Map.Entry<String, List<DataBindCollector>> entry : collGroup.entrySet()) {String key = entry.getKey();List<DataBindCollector> valueList = entry.getValue();//保存已缓存的数据绑定收集器Map<String, DataBindCollector> tempCache = new HashMap<>(64);//记录and拼接条件String[] andSql = {""};List<String> inValues = valueList.stream().filter(tmp -> {String unionKey = tmp.unionKey();//判断是否有缓存,有则从缓存中取DataBindCollector cache = CACHE.getIfPresent(unionKey);if (cache != null) {tempCache.put(unionKey, cache);return false;}boolean result = tmp.getConditionValue() != null;if (result) {andSql[0] = tmp.andCondition;}return result;}).map(collector -> parseForString(collector.getConditionValue())).distinct().collect(Collectors.toList());//从数据库查询值Map<String, Map<String, Object>> columnDataMap = queryDataMap(key,inValues,andSql[0]);if (CollUtil.isEmpty(columnDataMap) && CollUtil.isEmpty(tempCache)) {continue;}for (DataBindCollector collector : valueList) {// 如果有缓存,优先从缓存获取String unionKey = collector.unionKey();String conditionValue = String.valueOf(collector.getConditionValue());// 从缓存中获取结果值if (tempCache.containsKey(unionKey)) {DataBindCollector cache = tempCache.get(unionKey);// 设置查询出来的结果值到收集器collector.setValue(cache.getValue());}// 从数据库查询获取结果值else if (columnDataMap.containsKey(conditionValue)) {Map<String, Object> dataMap = columnDataMap.get(conditionValue);// 设置查询出来的结果值到收集器String upperCaseValueColumn = collector.getValueColumn().toUpperCase();String lowerCaseValueColumn = collector.getValueColumn().toLowerCase();Object value = Optional.ofNullable(dataMap.get(upperCaseValueColumn)).orElse(dataMap.get(lowerCaseValueColumn));collector.setValue(value);} else {continue;}// 放入到有过期时间的缓存当中去CACHE.put(unionKey, collector);// 为对象进行结果值的数据绑定collector.dataBind();}}}private static Map<String, Map<String, Object>> queryDataMap(String key, List<String> inValues, String andSql) {if(CollUtil.isEmpty(inValues)){return Collections.emptyMap();}// 分组的key组成公式:表名 + 查询的数据库表字段。详见内部类DataBindCollector的groupKey方法String[] tableColumnPair = key.split(String.valueOf(COLON));// 构建批量查询SQLSelectSqlBuilder selectSqlBuilder = new SelectSqlBuilder(tableColumnPair[0]).in(tableColumnPair[1], inValues);if (StrUtil.isNotBlank(andSql)) {selectSqlBuilder.lastSql(AND + andSql);}String sql = selectSqlBuilder.build();// 执行之前,进行SQL后置处理:例如数据权限的过滤处理
//        sql = SimpleSqlHandlerChain.handle(sql);log.info("[数据绑定查询sql为:] - {}", sql);// 表条件字段和行数据的映射return fastDataBindMapper().queryForList(sql).stream().collect(Collectors.toMap(map -> {String upperCaseKey = tableColumnPair[1].toUpperCase();String lowerCaseKey = tableColumnPair[1].toLowerCase();return String.valueOf(Optional.ofNullable(map.get(upperCaseKey)).orElse(map.get(lowerCaseKey)));}, m -> m, (oM, nM) -> oM));}/*** 查找需要数据绑定的字段* 查找以下三类字段:* 1:值为空且有数据绑定注解的注解* 2:值不为空,且为集合类* 3:值不为空且为bean** @param data 目标对象* @return 返回需要数据绑定的字段*/public static Field[] detectFields(Object data) {Class<?> objClass = data.getClass();//先找缓存if (CLASS_DATABIND_FIELD_MAP.containsKey(objClass)) {return CLASS_DATABIND_FIELD_MAP.get(objClass);}Field[] fields = Arrays.stream(ReflectUtil.getFields(objClass)).filter(tmp -> {tmp.setAccessible(true);try {Object value = tmp.get(data);//值为空且有数据绑定的注解boolean hasAnnotation = (value == null && hasAnnotation(tmp));//值不为空且为集合类boolean isColl = value != null && Arrays.asList(tmp.getType().getInterfaces()).contains(Collection.class);return hasAnnotation || isColl;} catch (IllegalAccessException e) {throw new RuntimeException(e.getMessage(), e);}}).toArray(Field[]::new);if (ArrayUtil.isEmpty(fields)) {return null;}//放入集合中CLASS_DATABIND_FIELD_MAP.put(objClass, fields);return fields;}private static boolean hasAnnotation(Field field) {return field.isAnnotationPresent(FastDataBindFiled.class);}private static List<DataBindCollector> buildCollectors(Object data) {if (data == null) {return Collections.emptyList();}//获取可能需要数据绑定的字段Field[] fields = detectFields(data);if (fields == null) {return Collections.emptyList();}List<DataBindCollector> dataBindCollectors = new ArrayList<>();for (Field field : fields) {try {field.setAccessible(true);//过滤调没有数据绑定的注解或置为空的过滤掉Object value = field.get(data);if (Arrays.asList(field.getType().getInterfaces()).contains(Collection.class)) {if (value == null) {continue;}Collection<?> dataValueColl = (Collection<?>) value;if (CollUtil.isEmpty(dataValueColl)) {continue;}Field[] dataValueFields = detectFields(dataValueColl.stream().iterator().hasNext());if (ArrayUtil.isNotEmpty(dataValueFields)) {dataBindCollectors.addAll(dataValueColl.stream().flatMap(tmp -> buildCollectors(tmp).stream()).collect(Collectors.toList()));}} else if (value == null) {FastDataBindProperty fastDataBindProperty = getFastDataBindProperty(field);DataBindCollector bindCollector = new DataBindCollector(fastDataBindProperty, field, data, null, null);if (bindCollector.conditionValue != null) {dataBindCollectors.add(bindCollector);}} else {Field[] detectFields = detectFields(data);if (ArrayUtil.isNotEmpty(detectFields)) {dataBindCollectors.addAll(buildCollectors(detectFields));}}} catch (IllegalAccessException e) {throw new RuntimeException(e);}}return dataBindCollectors;}/*** 解析字段值为符合mysql的字符串形式** @param val 字段值* @return 符合mysql的字符串形式*/private static String parseForString(Object val) {if (val == null || val instanceof Number || val instanceof Boolean) {return String.valueOf(val);}if (val instanceof String) {String varStr = val.toString();if (varStr.startsWith(SINGLE_QUOTE) && varStr.endsWith(SINGLE_QUOTE)) {return val.toString();}}return "'" + val + "'";}/*** 获取字段上数据绑定配置数据*/private static FastDataBindProperty getFastDataBindProperty(Field field) {FastDataBindProperty property = null;if (field.isAnnotationPresent(FastDataBindFiled.class)) {FastDataBindFiled dataBindFiled = field.getDeclaredAnnotation(FastDataBindFiled.class);Optional<FastDataBindProperty> fastDataBindPropertyOpt = fastDataBindPropertyFactory(dataBindFiled.config());if (fastDataBindPropertyOpt.isPresent()) {property = fastDataBindPropertyOpt.get();}String valueColumn = dataBindFiled.valueColumn();if (StringUtils.isNotBlank(valueColumn)) {Optional.ofNullable(property).ifPresent(p -> p.setValueColumn(valueColumn));}String conditionProperty = dataBindFiled.conditionProperty();if (StringUtils.isNotBlank(conditionProperty)) {Optional.ofNullable(property).ifPresent(p -> p.setConditionProperty(conditionProperty));}String conditionColumn = dataBindFiled.conditionColumn();if (StringUtils.isNotBlank(conditionColumn)) {Optional.ofNullable(property).ifPresent(p

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • @component注解的分类
  • 【手写数据库内核组件】0201 哈希表hashtable的实战演练,多种非加密算法,hash桶的冲突处理,查找插入删除操作的代码实现
  • uni-app三部曲之三: 路由拦截
  • 【系统架构设计】计算机组成与体系结构(一)
  • alibaba EasyExcel 简单导出数据到Excel
  • 创建vue3项目
  • 侯捷C++面向对象高级编程(上)-9-扩展补充:类模板、函数模板及其他
  • 哪些独立站外链策略最有效?
  • 压测jmeter 插件 之 tps和响应时间图
  • mysql 一主多从环境搭建
  • (C++哈希表01)
  • java springboot监听事件和处理事件
  • C#委托事件的实现
  • RNN文献综述
  • CC2530寄存器编程学习笔记_点灯
  • [deviceone开发]-do_Webview的基本示例
  • [译]Python中的类属性与实例属性的区别
  • 【个人向】《HTTP图解》阅后小结
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • 08.Android之View事件问题
  • 10个最佳ES6特性 ES7与ES8的特性
  • Hibernate最全面试题
  • JAVA_NIO系列——Channel和Buffer详解
  • js算法-归并排序(merge_sort)
  • web标准化(下)
  • 给github项目添加CI badge
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 《天龙八部3D》Unity技术方案揭秘
  • 阿里云ACE认证之理解CDN技术
  • 蚂蚁金服CTO程立:真正的技术革命才刚刚开始
  • 昨天1024程序员节,我故意写了个死循环~
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • ​浅谈 Linux 中的 core dump 分析方法
  • #DBA杂记1
  • #java学习笔记(面向对象)----(未完结)
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • (2024,Flag-DiT,文本引导的多模态生成,SR,统一的标记化,RoPE、RMSNorm 和流匹配)Lumina-T2X
  • (7)STL算法之交换赋值
  • (Java入门)抽象类,接口,内部类
  • (php伪随机数生成)[GWCTF 2019]枯燥的抽奖
  • (八)Flask之app.route装饰器函数的参数
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (多级缓存)缓存同步
  • (佳作)两轮平衡小车(原理图、PCB、程序源码、BOM等)
  • (七)Activiti-modeler中文支持
  • (顺序)容器的好伴侣 --- 容器适配器
  • (原創) 物件導向與老子思想 (OO)
  • (转)【Hibernate总结系列】使用举例
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • .NET C#版本和.NET版本以及VS版本的对应关系
  • .NET Compact Framework 多线程环境下的UI异步刷新
  • .NET CORE Aws S3 使用
  • .Net Core 微服务之Consul(三)-KV存储分布式锁