Java 对象比较-拓展 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
huifer
V2EX    Java

Java 对象比较-拓展

  •  
  •   huifer Aug 28, 2020 2625 views
    This topic created in 2070 days ago, the information mentioned may be changed or developed.

    对象比较

    命题

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

    设计

    确定比较对象

    • 在这里使用 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 replies    2020-08-29 08:17:34 +08:00
    6666666666666666
        1
    6666666666666666  
       Aug 28, 2020
    想知道使用场景?
    huifer
        2
    huifer  
    OP
       Aug 29, 2020
    @6666666666666666 应用场景比较单一,如: 在修改表对象的时候可以进行一次比较,用来记录修改前后的数据差异 (便于阅读的差异描述)。
    About     Help     Advertise     Blog     API     FAQ     Solana     994 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 49ms UTC 22:18 PVG 06:18 LAX 15:18 JFK 18:18
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86