最近也是在写后台管理系统,系统中的列表展示总是少不了要展示各种字段,其中包括本表的字段,还有涉及其他表的外键字段,因为在本表中那些外键字段存储的一般是ID,但展示在页面的则需要是名称,所以对这些数据也是需要进行查询
对于这些数据,我就以我的经验来看,大概可以用以下几种方式来进行包装
1、连表查询
这是最简单,直接在sql上进行关联查询,这种方式算是比较普遍的,但这种写法的话就是sql较为臃肿,特别是关联字段比较多的,用这种方式会让后续的维护扩展有一定的难度
2、装饰器模式
第二个就是使用java的设计模式,对返回的数据列表进行包装,以下就是对包装的抽象类,具体就是通过继承这个类来对数据进行操作查询
这种方式主要是通过调用公用方法进行对数据的查询,将连表查询中的外键关联拆分出来,实现一定的解耦,而且对查询数据的复用性更好
public abstract class BaseControllerWrapper<T> { private T t; private List<T> list; public BaseControllerWrapper(T t){ this.t=t; } public BaseControllerWrapper(List<T> list){ this.list=list; } public T wrap(){ if(t!=null){ this.wrapTheObject(this.t); return this.t; } return null; } public List<T> wrapList(){ if(this.list!=null && !this.list.isEmpty()){ for (T t1 : this.list) { this.wrapTheObject(t1); } return this.list; } return null; } public abstract T wrapTheObject(T t); }
3、 AOP
这个实现方式的话,我的想法就是通过反射去执行的查询方法来达到数据包装的结果。具体实现就是
(1)先定义一个注解,用来标识数据包装
@Target({ ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DataFill { //需要包装的字段id String[] field() default {}; //包装对象的实体类全类名 String[] targetEntity() default {}; //包装的映射字段 String[] targetField() default {}; //忽略包装的字段 String[] ignoreField() default {}; }
(2) 定义字段包装常量池
(3)写AOP切面
protected void handleDataFill(final JoinPoint joinPoint, Object object) { //获取注解 DataFill dataFill = getAnnotationLog(joinPoint); if (dataFill == null) { return; } //从常量池获取的目标实体类 Map<String, String> targetEntity = new HashMap<>(DataFillConstants.getTargetEntity()); //从常量池获取的目标映射字段 Map<String, String> targetField = new HashMap<>(DataFillConstants.getTargetField()); //忽略字段不进行包装,暂不考虑多线程情况 if (dataFill.ignoreField().length>0){ String[] ignoreField = dataFill.ignoreField(); for (String s : ignoreField) { if (targetEntity.containsKey(s) && targetField.containsKey(s)){ targetEntity.remove(s); targetField.remove(s); } } } //解析 需一一对应 if (dataFill.field().length > 0 && dataFill.targetEntity().length == dataFill.field().length && dataFill.targetField().length == dataFill.field().length ) { String[] fields = dataFill.field(); for (int i = 0; i < fields.length; i++) { //若常用包装数据含有,则替换 targetEntity.put(fields[i], dataFill.targetEntity()[i]); targetField.put(fields[i], dataFill.targetField()[i]); } } //TODO 过滤当前主键 //数据处理 if (object instanceof com.github.pagehelper.Page) { List<Object> list = ((Page) object).getResult(); List<Map<String, Object>> mapList = BeanUtils.beansToMaps(list); for (Map<String, Object> map : mapList) { handleData(map, targetEntity, targetField); } } else if (object instanceof ArrayList) { List<Map<String, Object>> mapList = BeanUtils.beansToMaps((List<Object>) object); for (Map<String, Object> map : mapList) { handleData(map, targetEntity, targetField); } } else if (object instanceof BaseEntity) { Map<String, Object> map = BeanUtils.beanToMap(object); handleData(map,targetEntity,targetField); } } private void handleData(Map<String, Object> map, Map<String, String> targetEntity, Map<String, String> targetField) { Set<String> entityKey = targetEntity.keySet(); Iterator<String> iterator = entityKey.iterator(); //遍历是否包含需要包装的字段 try { while (iterator.hasNext()) { String key = iterator.next(); //是否包含实体类与映射字段 if (map.containsKey(key) && targetEntity.containsKey(key) && map.get(key) != null) { //指定实体类,读取该包下的Iservice接口中根据id查询的方法 //TODO 拆解成动态传入 Class<?> clazz = Class.forName(targetEntity.get(key)); Object methodData = MethodUtils.getMethodData(clazz, Long.valueOf(map.get(key).toString())); if (methodData == null) { continue; } Map<String, Object> bean = BeanUtils.beanToMap(methodData); map.put(targetField.get(key), bean.get(targetField.get(key))); } } } catch (Exception e) { e.printStackTrace(); } } /** * 是否存在注解,如果存在就获取 */ private DataFill getAnnotationLog(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method != null) { return method.getAnnotation(DataFill.class); } return null; }
(4)通过反射执行方法工具类
大致思路就是这样,这种局限性比较大,固定了包结构与指定方法,不过好处就是写的代码量大大减少,在查询列表的方法直接加个注解就能实现包装,功能再强大一点也是可以的,比如说动态传参、动态方法那些,但我觉得没有必要,太多功能的话,执行逻辑会复杂很多,得不偿失,这个的应用场景只是为关联字段做包装,其他逻辑的就需要再写了
以上的话大概就是我写过的对数据列表的三种包装方式,工作年限不高,资历尚浅,还是需要多多歇息