对象比拟

命题

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

设计

确定比拟对象

  • 在这里应用 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`的实体对象
@Componentpublic 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`注解的类.classvalue:     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;}
@Servicepublic 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