提供一种业务系统非核心信息不连表查询解决方案

一种业务系统非核心信息不连表查询解决方案

本文针对java开发且采用前后端分离的开发模式,非java开发可能作用不大。同时数据库以mysql为例,部分表述只做示例,并非严谨的mysql语句。

普通的业务系统开发过程中,下面描述的这种需求应该是比较常见的。一个申请单,需要显示申请人名字,审核人名字。

这里涉及到两张表:申请单(t_apply), 用户(t_user),后台数据表我们可能会这么设计:

// 方案一
t_apply(
    apply_id,
    apply_no,
    ***
    apply_user_id,
    apply_user_name,
    apply_auditor_id,
    apply_auditor_name
    ***
)
t_user(
    user_id,
    user_name
)

也可能这么设计

// 方案二
t_apply(
    apply_id,
    apply_no,
    ***
    apply_user_id,
    apply_auditor_id
    ***
)
t_user(
    user_id,
    user_name
)

方案一冗余了申请人名字和审核人名字字段,很好,在查询的时候不需要连表查询。但要考虑,如果这个用户改名了呢,申请单的名字要不要做修改?如果不需要,保存并在后期显示当时的快照即可,那么本文可以跳过了。如果需要,那么冗余就显得没有必要了。因为这里更新的成本有些大。

方案二存的是ID,没有冗余的字段,很好,只是查询的时候要连表查。稍显麻烦。

连表对于数据库,是一个比较大的性能开销。多数企业做应用架构并未考虑读写分离,连表过多更会产生影响,阿里的开发规范也有提到连表查询最好不要超过3张表,尤其是这种非核心但又非得要的信息,连表查询更加显得不重要且耗数据库性能。

1、当然这个问题也有其他的解决方案

方案W:把问题丢给前端。后台做基础数据查询,申请单、用户都返回,前端根据用户ID,去找用户名字,然后显示。这种方式,估计前端看了想打人。

方案S:把数据库的性能转移给java。不连表查询,任何查询都不连表。举个例子,对于申请单列表:

  1. 查到所有申请单,一个列表:List applies。
  2. 遍历 applies 得到一个用户ID列表List userIds。
  3. 根据 userIds 查到List users
  4. 遍历 applies ,根据 applyUserId , 遍历 users ,找到 applyUserName。这一步也可事先将users转为键值为id的map,然后更快定位数据。

如此,将数据库的压力转移给了java。这点查询与循环,对于java来说,还是没啥问题的。然而,此刻java开发人员就没有那么高兴了。不让连表查询,每次这么查询遍历,够累的。

2、不想重复劳动,那就再想想办法

这个填塞的过程其实是很常见的,比如再来一个公司,申请单有一个归属公司 company_id , 公司名字存在基础表 t_company。申请单要显示公司名字,java开发人员在用方案S进行数据查询的时候,公司和用户的操作,步骤完全一致,只是对应的实体不同。

因为懒,不想写重复的代码,哪怕是重复逻辑的代码,所以就得想想办法

好,来说说本人想到的方案:那就是抽离这一部分填塞的业务,用一个横切来实现。把方案W和方案S结合一下。基础数据(如用户)提供基本查询方法,业务开发(如申请单)调用基本查询方法,实现数据的填塞。

3、下面讲讲具体怎么做

2个注解,一个加在视图对象字段上,表达该字段需要从别的表中查。一个加在控制层方法上,表达该方法需要处理返回值字段连表查询。

字段层注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Converted {

    @ApiParam("依赖字段,根据当前类实体存储的字段,得到目标字段")
    String dependProperty();

    @ApiParam("BeanService,如 UserService.class")
    Class<? extends Object> bean();

    @ApiParam("形式为:List<T> refMethod(List<dependProperty>) , 或者Map<String,String> refMethod(List<dependProperty>)的关联实体方法")
    String refMethod() default "listByIds";

    @ApiParam("关联实体组成Map的key值")
    String refKey() default "id";

    @ApiParam("如果method返回值为List<T>使用T.getLabel()如T.getName() 给当前关联实体赋值")
    String refLabel() default "name";
    
    @ApiParam("关联实体组成Map的key值类型")
    Class<? extends Object> refKeyClass();

    @ApiParam("未能成功转换给的默认值")
    String defaultValue() default "";

    
}

以刚刚的申请单为例,解释下各个标注含义。

  1. dependProperty即为apply.applyUserId,其存在于申请表,select * from t_apply单表查询就能得到。
  2. bean即为用户服务,需要是能取到的spring的bean。其提供查询方法,能查询到用户信息。
  3. refMethod即为用户提供的能查到用户信息的方法名。这个方法名参数类型必须是List,List的泛型类型必须是dependProperty的类型。
  4. refKey即为user.userId。即apply.applyUserId 对应到user的字段名。
  5. refLabel即为user.userName。及目标字段,最后要显示出来的字段内容。
  6. refKeyClass,即为dependProperty的类型。这里实际上可以通过反射取到,后面也可能将其优化掉。
    7.defaultValue的意思是:没取到咋办,设个默认值

方法层注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldConversion {

}

没啥好说的,做个控制,不是所有的方法都需要连表查,加上才来查。

好,接下来看下切片处理咋写,思路跟方案S差不多。这部分内容太长,我展示下关键代码,主要是用反射取值,设置,加一些泛型处理。

/**
 * Created by tuofan 
 */
@SuppressWarnings("unchecked")
public class FieldConvertUtils {

    private static Logger logger = LoggerFactory.getLogger(FieldConvertUtils.class);

    private FieldConvertUtils() {
    }

    /**
     * @param list
     */
    public static <T> void convertList(List list) throws NoSuchFieldException, NoSuchMethodException {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        if (list.get(0) == null) {
            return;
        }
        Field[] fields = FieldConvertUtils.getFields(list.get(0));
        if (fields == null || fields.length == 0) {
            return;
        }
        Map<Field, Map<T, String>> refBeanValueMap = FieldConvertUtils.getFieldFeignValueMap(list, fields);
        FieldConvertUtils.convertValue(list, fields, refBeanValueMap);
    }


    private static Field[] getFields(Object object) {
        return object.getClass().getDeclaredFields();
    }

    /**
     * 获取列表中要转换所有key和value
     *
     * @param list
     * @param fields
     * @return
     */
    private static <T> Map<Field, Map<T, String>> getFieldFeignValueMap(List list, Field[] fields) throws NoSuchFieldException, NoSuchMethodException {
        // 存放每个字段,转换前和转换后的对应值
        Map<Field, Map<T, String>> refFiledValueMap = Maps.newHashMap();
        for (Field field : fields) {
            Converted converted = field.getAnnotation(Converted.class);
            if (converted == null) {
                continue;
            }
            List<T> keyList = extractList(list, field);
            Map<T, String> keys2ValuesMap = convertKeys2Values(keyList, field);
            refFiledValueMap.put(field, keys2ValuesMap);
        }
        return refFiledValueMap;
    }

    private static <T> Map<T, String> convertKeys2Values(List<T> keys, Field field) throws NoSuchMethodException, NoSuchFieldException {
        if (CollectionUtils.isEmpty(keys)) {
            return Maps.newHashMap();
        }
        Converted converted = field.getAnnotation(Converted.class);
        Object beanService = SpringUtils.getBean(converted.bean());
        Object[] args = {keys};
        Method refMethod = getMethod(beanService.getClass(), converted.refMethod());
        Class returnClazz = refMethod.getReturnType();
        // 返回值是map
        if (returnClazz.isAssignableFrom(Map.class)) {
            return (Map<T, String>) ReflectionUtils.invokeMethod(refMethod, beanService, args);
        }
        // 返回值是list
        if (returnClazz.isAssignableFrom(Collection.class)) {
            Collection collection = (Collection) ReflectionUtils.invokeMethod(refMethod, beanService, args);
            if (CollectionUtils.isEmpty(collection)) {
                return Maps.newHashMap();
            }
            return convertList2MapFilterNull(collection, converted.refKey(),
                    converted.refLabel());
        }
        logger.error("返回值类型={}暂不支持转换,目前仅支持 Collection 和 Map ", returnClazz.getName());
        return Maps.newHashMap();
    }


    private static <T> Map<T, String> convertList2MapFilterNull(Collection<?> collection, String keyProperty, String valueProperty) throws NoSuchFieldException {
        Map<T, String> map = Maps.newHashMap();
        for (Object ele : collection) {
            Field fKey = ele.getClass().getDeclaredField(keyProperty);
            T key = extractTValue(ele, fKey);
            Field fValue = ele.getClass().getDeclaredField(valueProperty);
            ReflectionUtils.makeAccessible(fValue);
            String value = (String) ReflectionUtils.getField(fValue, ele);
            map.put(key, value);
        }
        return map;
    }


    /**
     * 作为beanService 的参数
     */
    private static <T> List<T> extractList(List list, Field field) throws NoSuchFieldException {
        List<T> keyList = Lists.newArrayList();
        Converted converted = field.getAnnotation(Converted.class);
        for (Object returnValue : list) {
            // 获取要转换的key值
            T key = extractKey(returnValue, converted);
            if (key != null) {
                keyList.add(key);
            }
        }
        return keyList;
    }

    /**
     * 获取key,注解上有dependProperty属性,则取这个属性的值,否则就是当前filed的值
     *
     * @param returnValue
     * @param converted
     * @return
     */
    private static <T> T extractKey(Object returnValue, Converted converted) throws NoSuchFieldException {
        Field field = returnValue.getClass().getDeclaredField(converted.dependProperty());
        return extractTValue(returnValue, field);
    }

    private static <T> T extractTValue(Object returnValue, Field field) throws NoSuchFieldException {
        ReflectionUtils.makeAccessible(field);
        Object obj = ReflectionUtils.getField(field, returnValue);
        if (obj == null) {
            return null;
        }

        return (T) obj;
    }

    /**
     * 根据取到的值进行转换
     *
     * @param list
     * @param refFiledValueMap
     */
    private static <T> void convertValue(List list, Field[] fields, Map<Field, Map<T, String>> refFiledValueMap) throws NoSuchFieldException {
        for (Object returnValue : list) {
            for (Field field : fields) {
                Converted converted = field.getAnnotation(Converted.class);
                if (converted == null) {
                    continue;
                }
                T key = extractKey(returnValue, converted);
                ReflectionUtils.makeAccessible(field);
                if (refFiledValueMap.containsKey(field) && refFiledValueMap.get(field).containsKey(key)) {
                    ReflectionUtils.setField(field, returnValue, refFiledValueMap.get(field).get(key));
                } else {
                    ReflectionUtils.setField(field, returnValue, converted.defaultValue());
                }
            }
        }
    }

    /**
     * @param clazzT
     * @param methodName
     * @return
     */
    private static Method getMethod(Class clazzT, String methodName) {
        if (clazzT == null || clazzT == Object.class || StringUtils.isEmpty(methodName)) {
            return null;
        }
        for (; clazzT.getSuperclass() != Object.class; clazzT = clazzT.getSuperclass()) {
            Method[] methods = clazzT.getDeclaredMethods();
            for (Method m : methods) {
                if (m.getName().equals(methodName)) {
                    return m;
                }
            }
        }
        return null;
    }
}
4、最后看看怎么用

vo字段上加注解

@ApiModelProperty(value = "审核人ID")
private Long auditorId;

@Converted(dependProperty = "auditorId", refKeyClass = Integer.class, bean = MobileUserService.class, refLabel = "userName")
@ApiModelProperty(value = "审核人姓名")
private String auditorName;

controller上加标签

@PostMapping("listPage")
@FieldConversion
public ResultVO<IPage<ModelVO>> listPage(@RequestBody ModelQuery modelQuery) {
    return ** 省略业务逻辑代码 **;
}

总的来说,就是将公共的操作抽象出来,用切片的方式实现,让代码更加整洁。

转载于:https://www.cnblogs.com/tuofan/p/11378856.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一级 二级 三级 四级 五级 "有学习愿望,能够在指导或者要求下进行学习。 1、能够通过示范式、教授式学习或者指定的学习资源掌握做好自身岗位工作所需要的知识、技能、工具和信息等。" "积极的学习愿望,主动学习,保持专业知识技能的更新。 1、能够自学或主动向他人学习本业务领域内的知识、技能。 2、了解专业领域的最新发展情况并努力在工作中运用,创造符合岗位要求的绩效。" "主动学习本业务领域知识,能够融会贯通、积极共享。 1、积极寻求和创造学习机会,善用学习资源,超越岗位需求,学习自身业务领域以及相关领域的知识,具有能够运用所学知识举一反三,能够与团队成员交流和分享相关知识、经验,创造良好绩效。" "超越岗位工作需求,学习本业务及相关业务领域知识,利用团队外的知识提高团队业务知识、技能。 1、能够充当起团体外的知识资源协调者的角色,充分利用起团队外的知识资源提升自身业务知识、技能。 2、通过知识共享帮助团队其他成员提高,能使团队的业务水平居于公司其他团队业务水平之上,并有一定的成果体现。" "自度度人,影响团队向学习型团队转变,并成为同行标杆。 1、能够带动团队其他成员主动学习,营造团队学习氛围,使学习成为团队的一种习惯。 2、自身业务领域权威,并通晓一定相关业务领域知识,带动团队的业务水平居于组织相同团队前列,成为标杆。" "缺乏主动沟通的意愿,不善于表达。 如情不得已,很少主动沟通和协调。沟通过程中常常不注意倾听,或者表达不清晰,难以理解,需要反复说明,或者他人协助解释。" "有沟通意愿,能够准确理解和被理解。 具有良好的沟通意愿,多数情况下都能够有效倾听和理解对方。能准确无误、简练的表达自己的观点,能够进行简单的协调。" "主动沟通协调,有效开展工作。 总能准确无误、逻辑清晰、简练的表达自己的观点,准确的领悟对方观点,并能引导对方沿着自己的思路展开交流;当工作出现问题,总能积极的想方设法去寻求帮助,协调工作群体中的其他成员共同解决问题,使工作正常进行。" "良好的沟通协调技巧,讲求方式方法 掌握并运用有效沟通的基本原则和技巧,如事前知会,事中沟通、协调,事后汇报,使得工作在团队或跨团队中协调进行,达成共同目标。能够善于因人而异,采取针对性沟通方式方法。经常能够通过有效沟通和协调解决别人感到难以解决的问题,沟通能力受到周围同事普通认可。" "惠已及人,重大事件和问题有效沟通和协调。 与团队分享有效沟通和协调的经验和方法,带动团队沟通协调能力提升。对于突发或复杂问题,能够协调公司的稀缺资源,促成有力的解决方案。"

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值