利用aop高效的实现字典,和外键属性的转译

/ 技术收藏 / 1 条评论 / 919浏览

我们现在设计有如下两张表(其中,学生表位班级表的子表)

班级表(sys_class):
属性:id name  head_teacher ...

学生表(sys_student)
属性:id class_id name gender ... 

此时,我们有某个需求,需要查询学生,并且需要学生的班级名称。

我们一般的做法有以下几种:

(1). 使用sql的join来实现

select t1.*,t2.name as class_name from sys_student t1 left join sys_class t2 on t1.class_id= t2.id

join的本质还是创建了虚拟表,对结果做笛卡儿积。这是非常恐怖的,笛卡儿积会导致数据成倍增长,占用大量的资源,导致大幅降低数据库的吞吐量。

(2). 使用程序来实现关联 做法是先查询全部学生,再查询全部班级,使用两层for循环或者是使用map缓存班级id和班级名称的对应关系。总之,该方法所有逻辑处理均在代码中完成,数据库只提供基础的查询功能。

数据库是维持系统稳定的重要基础设施,大多数情况下,此方法比(1)中的方法要更优秀的,只需做简单的数据库io,这样数据库系统就能用节省下来的资源去支持更多的并发。缺点也很明显,相比第一种方式,需要编写代码来实现逻辑,尤其是复杂的系统,代码量可能更大。

笔者使用的是第三种方法,现在大多数的ORM框架都默认实现了单表的查询,在此基础上,笔者选择方法2,但是基于aop来实现关系字段的转译。

代码如下

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(DictS.class)
public @interface Dict {

    String dicTable(); //字典表

    String dicText(); //字典表中需要翻译到的text

    String dicColumn(); //字典表的列

    String targetColumnName() default ""; //新增的列名,默认为{code}加上_text
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DictS {

    Dict[] value();

}

/**
 * @Description 注解翻译字典
 * @since 2020-12-25
 */
@Aspect
@Component
@Slf4j
public class TranslateDictAspect {

    @Resource
    CommonApiService commonApiService;

    @Resource
    ObjectMapper objectMapper;

    // 定义切点Pointcut
    @Pointcut("execution(public * com.trusted.software..*.*Controller.*(..))")
    public void executeService() {
    }

    @Around("executeService()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        UnUseDict unUseDict = signature.getMethod().getAnnotation(UnUseDict.class);
        if(unUseDict!=null){
            return pjp.proceed();
        }
        long time1=System.currentTimeMillis();
    	//执行controller
        Object result = pjp.proceed();
        long time2=System.currentTimeMillis();
        log.debug("controller 耗时:"+(time2-time1)+"ms");
        long start=System.currentTimeMillis();
        //翻译controller返回结果
        try {
            this.translate(result);
        } catch (JsonProcessingException | JSONException e) {
            //同时捕获jackson,fastjson 转换异常
            //处理失败,静默原样返回
            log.debug("非json格式数据->",e);
        }
        long end=System.currentTimeMillis();
        log.debug("翻译  耗时"+(end-start)+"ms");
        return result;
    }

    /**
     * TODO 批量转换
     * @param result
     * @throws IllegalAccessException
     */
    private void translate(Object result) throws JsonProcessingException {
        if (result instanceof R) {
            Object data = ((R) result).getData();
            //处理集合
            if(data instanceof Collection) {
                //这里处理一层集合
                Collection collection = (Collection)data;
                List<JSONObject> resultList = new ArrayList<>();
                for (Object obj : collection) {
                    JSONObject item = handleItem(obj);
                    resultList.add(item);
                }
                //数据回填
                ((R)result).setData(resultList);
            }else{
                //分页数据
                if(data instanceof IPage){
                    List records = ((IPage) data).getRecords();
                    List<JSONObject> resultList = new ArrayList<>();
                    for (Object obj : records) {
                        JSONObject item = handleItem(obj);
                        resultList.add(item);
                    }
                    //数据回填
                    ((IPage) data).setRecords(resultList);
                }else {
                    if(null!=data) {
                        //非分页单条数据
                        JSONObject item = handleItem(data);
                        //数据回填
                        ((R) result).setData(item);
                    }
                }
            }
        }
    }

    public JSONObject handleItem(Object obj) throws JsonProcessingException {
        String json="{}";
        //使用ObjectMapper处理一遍-时间格式化问题
        json = objectMapper.writeValueAsString(obj);
        JSONObject item = JSONObject.parseObject(json);
        Class<?> clazz = obj.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            if (declaredField.isAnnotationPresent(Dict.class)) {
                String fieldName = declaredField.getName();
                declaredField.setAccessible(true);
                Object value = null;
                try {
                    value = declaredField.get(obj);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                Dict dict = declaredField.getAnnotation(Dict.class);
                dicItem(dict,value,item,fieldName);
            }else if (declaredField.isAnnotationPresent(DictS.class)){
                String fieldName = declaredField.getName();
                DictS dictS = declaredField.getAnnotation(DictS.class);
                Dict[] dicArr = dictS.value();
                for (Dict dict:dicArr) {
                    declaredField.setAccessible(true);
                    Object value = null;
                    try {
                        value = declaredField.get(obj);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                    dicItem(dict,value,item,fieldName);
                }
            }
        }
        return item;

    }

    public void dicItem(Dict dict,Object value,JSONObject item,String fieldName){
        String table = dict.dicTable();
        String text = dict.dicText();
        String column = dict.dicColumn();
        String targetColumnName = dict.targetColumnName();
        //此处做了reids缓存
        DictModel dictModel = commonApiService.queryTableDictTextByKey(table, text, column, value);
        if (dictModel!=null) {
            String translatedText = dictModel.getText();
            if(!StrUtil.isEmpty(targetColumnName)) {
                item.put(targetColumnName, translatedText);
            }else{
                item.put(fieldName + "_text", translatedText);
            }
        }
    }



}

这样,我们在业务系统中,就可以只通过注解@Dict的方式,减少大量的代码。配合上aop,就实现了外键的转译。在实际开发中,还可以结合sql,查询vo,对vo再进行转译,大量降低子查询,join查询,使用起来是比较灵活的。

  1. 这个思路很好,点赞学习 已经用到项目里了👍

    回复