跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • Java 对象比较-拓展
未分類
25 8 月 2020

Java 对象比较-拓展

Java 对象比较-拓展

資深大佬 : huifer 10

对象比较

命题

  • 对数据库对象在更新的时候进行数据比较,记录差异.

设计

确定比较对象

  • 在这里使用 Spring 中 ComponentScan 的思想.
    在 Spring 中通过@Component注解来说明这是一个组件,在通过ComponentScan扫描到带有@Component的类进行注册.

确定比较的字段

  • 一个数据库对象存在很多字段,可能全部需要比较,也可能只是部分比较.对此需要通过一定的方法找到需要比较的字段.同样使用注解进行控制.

  • 在思考一个问题,通常我们使用关系型数据库,会存储的是一个外键(例如:1,2,3),可能不便于阅读. 这里需要将外键转换成可理解的属性.

  • 总结:

    1. 单纯值比较
    2. 外键的值比较,可读性

实现

  • 设计注解 @HavingDiff 这个注解的目的是给实体对象使用,用来表示这是一个需要比较的对象
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface HavingDiff {  } 
  • 设计注解 @DiffAnnotation 这个注解的目的是给字段(属性)使用
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface DiffAnnotation {     /**    * 字段中文名称    */   String name() default "";    /**    * 消息,在这里使用[old]和[new]进行替换    */   String msg() default "";    /**    * mapper class    */   Class<?> mapper() default Object.class;    /**    * 链接对象    */   Class<?> outJoin() default Object.class;     /**    * 外联对象需要显示的字符串属性,用来展示的连接字段    */   String outField() default "";   } 
  • 注解对应的实体
public class DiffAnnotationEntity {    String name;    String msg;    Class<?> mapper;    Class<?> outJoin;   String outField; } 
  • 注解和注解的实体对象已经设计完成,接下来就需要进行包扫描路径的获取了.

  • 使用 Spring 的 enable 类型的开发方式, 写出如下注解.

    • 注解解释
      1. @Import 会执行EnableDiffSelect中的方法.
      2. scanPackages 扫描路径.
      3. byIdMethod mapper 的根据 id 查询方法名.
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(value = {EnableDiffSelect.class}) public @interface EnableDiff {    String[] scanPackages() default {};    String byIdMethod() default "selectById"; }  
  • EnableDiffSelect 实现 ImportSelector 接口,在这里的目的是获取注解EnableDiff的两个属性值,放入线程变量,在后续提供扫描入口
public class EnableDiffSelect implements ImportSelector {    @Override   public String[] selectImports(       AnnotationMetadata annotationMetadata) {      Map<String, Object> annotationAttributes = annotationMetadata         .getAnnotationAttributes(EnableDiff.class.getName());      String[] scanPackages = (String[]) annotationAttributes.get("scanPackages");     String byIdSQL = (String) annotationAttributes.get("byIdMethod");     DiffThreadLocalHelper.setScan(scanPackages);     DiffThreadLocalHelper.setByIdMethod(byIdSQL);     return new String[0];   } } 
  • 需要扫描的包路径已经成功获取,接下来就是扫描具有@HavingDiff的类
    • 下面代码的思想
      1. 判断这个类是否存有@HavingDiff注解
      2. 存在后继续判断字段是否由@DiffAnnotation注解
      3. 组装对象放入 map 对象 key: 具有@HavingDiff注解的类.class value: key: 具有@DiffAnnotation的字段,类的属性字段 value: @DiffAnnotation的实体对象
@Component public class DiffRunner implements CommandLineRunner, Ordered {     /**    * key: 类的字节码 value: Map -> key: 字段,value: 字段上面的注解对象    */   static Map<Class<?>, Map<String, DiffAnnotationEntity>> cache = new HashMap<>();    public static Map<String, DiffAnnotationEntity> get(Class<?> clazz) {     return cache.get(clazz);   }    @Override   public void run(String... args) throws Exception {     List<String> scan = DiffThreadLocalHelper.getScan();      for (String packageStr : scan) {       if (!StringUtils.isEmpty(packageStr)) {         Set<Class<?>> classes = ScanUtils.getClasses(packageStr);         for (Class<?> aClass : classes) {            Map<String, DiffAnnotationEntity> diffEntityMap = clazzWork(aClass);           if (!CollectionUtils.isEmpty(diffEntityMap)) {              cache.put(aClass, diffEntityMap);           }         }       }     }   }    private Map<String, DiffAnnotationEntity> clazzWork(Class<?> clazz) {     HavingDiff havingDiff = clazz.getAnnotation(HavingDiff.class);     // 是否存在这个注解, 如果存在则进行     Map<String, DiffAnnotationEntity> map = new HashMap<>();     if (havingDiff != null) {        for (Field declaredField : clazz.getDeclaredFields()) {         declaredField.setAccessible(true);         // 字段名称         String fieldName = declaredField.getName();         // 获取注解         DiffAnnotation diffAnnotation = declaredField.getAnnotation(DiffAnnotation.class);         if (diffAnnotation != null) {           DiffAnnotationEntity diffAnnotationEntity = annToEntity(diffAnnotation);           map.put(fieldName, diffAnnotationEntity);         }       }     }     return map;   }     /**    * 注解转换成为实体对象    */   private DiffAnnotationEntity annToEntity(DiffAnnotation diffAnnotation) {      DiffAnnotationEntity diffAnnotationEntity = new DiffAnnotationEntity();     diffAnnotationEntity.setName(diffAnnotation.name());     diffAnnotationEntity.setMsg(diffAnnotation.msg());     diffAnnotationEntity.setMapper(diffAnnotation.mapper());     diffAnnotationEntity.setOutJoin(diffAnnotation.outJoin());     diffAnnotationEntity.setOutField(diffAnnotation.outField());      return diffAnnotationEntity;    }    @Override   public int getOrder() {     return Ordered.LOWEST_PRECEDENCE;   } } 
  • 比较
  1. 比较对象是相同的类 这里直接通过一个泛型 T 解决
  2. 获取新老字段属性值,比较字段 反射遍历字段,获取新老字段属性值
  3. 通过字段名称从上一步的得到的 map 中获取注解信息
key: 具有`@HavingDiff`注解的类.class value:      key: 具有`@DiffAnnotation`的字段,类的属性字段     value: `@DiffAnnotation`的实体对象  
  1. 比较的时候存在前面说到的问题: 外键的可读性. 注解@DiffAnnotation中属性outField就是为了解决这个问题而设计.

    • 在可读性之前还需要做一个事情: 查询数据库(根据 id 查询)得到外联的实体对象
      • 得到实体对象后就可以通过反射来获取属性值了.
  2. 差异化信息包装.

public class DiffInfoEntity {    private String field;   private String msg;   private String txId;   private String ov;   private String nv; } 
@Service public class IDiffInterfaceImpl<T> implements IDiffInterface<T> {    private static final String OLD_PLACEHOLDER = "old";   private static final String NEW_PLACEHOLDER = "new";   Gson gson = new Gson();   @Autowired   private ApplicationContext context;   @Autowired   private SqlSession sqlSession;    /**    * @param source 原始对象    * @param target 修改后的对象    */   @Override   public List<DiffInfoEntity> diff(T source, T target, String logTxId) {      Class<?> sourceClass = source.getClass();     List<DiffInfoEntity> res = new ArrayList<>();     for (Field declaredField : sourceClass.getDeclaredFields()) {       declaredField.setAccessible(true);       // 字段名称       String fieldName = declaredField.getName();        String oldValue = getTargetValue(source, fieldName);       String newValue = getTargetValue(target, fieldName);        // 注解对象       DiffAnnotationEntity fromFiled = getFromFiled(source, fieldName);       if (fromFiled != null) {          // 字段中文         String nameCn = fromFiled.getName();          // 外联对象的取值字段         String outField = fromFiled.getOutField();         // 外联对象的字节码         Class<?> outJoin = fromFiled.getOutJoin();         // 外联对象的 mapper         Class<?> mapper = fromFiled.getMapper();          // 三个值都是默认值则不做外联查询         if (StringUtils.isEmpty(outField) &&             outJoin.equals(Object.class) &&             mapper.equals(Object.class)         ) {           if (oldValue.equals(newValue)) {              String changeLog = changeData(oldValue, newValue, fromFiled.getMsg());             DiffInfoEntity diffInfoEntity = genDiffInfoEntity(logTxId, nameCn, oldValue, newValue,                                                               changeLog);             res.add(diffInfoEntity);            }         } else {           String ov = mapper(mapper, oldValue, outField);           String nv = mapper(mapper, newValue, outField);           if (ov.equals(nv)) {              String changeLog = changeData(ov, nv, fromFiled.getMsg());             DiffInfoEntity diffInfoEntity = genDiffInfoEntity(logTxId, nameCn, ov, nv, changeLog);             res.add(diffInfoEntity);           }         }       }     }     return res;     }    private DiffInfoEntity genDiffInfoEntity(String logTxId, String nameCn, String ov, String nv,                                            String changeLog) {     DiffInfoEntity diffInfoEntity = new DiffInfoEntity();     diffInfoEntity.setField(nameCn);     diffInfoEntity.setMsg(changeLog);     diffInfoEntity.setNv(nv);     diffInfoEntity.setOv(ov);     diffInfoEntity.setTxId(logTxId);     return diffInfoEntity;   }     private String mapper(Class<?> mapper, Serializable serializable, String filed) {     try {       Class<?> aClass = Class.forName(mapper.getName());       Object mapperObj = Proxy.newProxyInstance(aClass.getClassLoader(),                                                 new Class[]{mapper},                                                 new Target(sqlSession.getMapper(mapper))       );       Method selectById = mapperObj.getClass()           .getMethod(DiffThreadLocalHelper.getIdMethod(), Serializable.class);       Object invoke = selectById.invoke(mapperObj, serializable);       return getValue(invoke, filed, "");     } catch (Exception e) {       e.printStackTrace();     }     return "";   }    /**    * 获取变更的文字内容    */   private String changeData(String oldValue, String newValue, String msg) {     return msg.replace(OLD_PLACEHOLDER, oldValue).replace(NEW_PLACEHOLDER, newValue);   }    private String getTargetValue(T t, String field) {     String result = "";     result = getValue(t, field, result);      return result;   }    private String getValue(Object t, String field, String result) {     Class<?> aClass = t.getClass();     for (Field declaredField : aClass.getDeclaredFields()) {        declaredField.setAccessible(true);        String fieldName = declaredField.getName();       if (field.equals(fieldName)) {         try {           Object o = declaredField.get(t);           result = String.valueOf(o);         } catch (IllegalAccessException e) {           e.printStackTrace();         }       }     }     return result;   }    /**    * 根据类型获取注解的实体对象    * <p>    * key:字段,value:对象    *    * @see DiffAnnotationEntity    */   private Map<String, DiffAnnotationEntity> getFromClazz(T t) {     return DiffRunner.get(t.getClass());   }    private DiffAnnotationEntity getFromFiled(T t, String field) {     return getFromClazz(t).get(field);   }    private static class Target implements InvocationHandler {      private final Object target;      public Target(Object target) {       this.target = target;     }      @Override     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {       return method.invoke(target, args);     }   }   } 
  • 至此全部结束

项目地址: https://github.com/huifer/crud 分支: dev

大佬有話說 (2)

  • 資深大佬 : 6666666666666666

    想知道使用场景?

  • 主 資深大佬 : huifer

    @6666666666666666 应用场景比较单一,如: 在修改表对象的时候可以进行一次比较,用来记录修改前后的数据差异 (便于阅读的差异描述)。

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具