对象比拟
命题
- 对数据库对象在更新的时候进行数据比拟,记录差别.
设计
确定比拟对象
- 在这里应用 Spring 中
ComponentScan
的思维.
在 Spring 中通过@Component
注解来阐明这是一个组件,在通过ComponentScan
扫描到带有@Component
的类进行注册.
确定比拟的字段
- 一个数据库对象存在很多字段,可能全副须要比拟,也可能只是局部比拟.对此须要通过肯定的办法找到须要比拟的字段.同样应用注解进行管制.
- 在思考一个问题,通常咱们应用关系型数据库,会存储的是一个外键(例如:1,2,3),可能不便于浏览. 这里须要将外键转换成可了解的属性.
总结:
- 单纯值比拟
- 外键的值比拟,可读性
实现
- 设计注解
@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 类型的开发方式, 写出如下注解.
注解解释
@Import
会执行EnableDiffSelect
中的办法.scanPackages
扫描门路.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
的类上面代码的思维
- 判断这个类是否存有
@HavingDiff
注解 - 存在后持续判断字段是否由
@DiffAnnotation
注解 组装对象放入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; }}
- 比拟
- 比拟对象是雷同的类
这里间接通过一个泛型T解决 - 获取新老字段属性值,比拟字段
反射遍历字段,获取新老字段属性值 - 通过字段名称从上一步的失去的map中获取注解信息
key: 具备`@HavingDiff`注解的类.classvalue: key: 具备`@DiffAnnotation`的字段,类的属性字段 value: `@DiffAnnotation`的实体对象
比拟的时候存在后面说到的问题: 外键的可读性.
注解@DiffAnnotation
中属性outField
就是为了解决这个问题而设计.在可读性之前还须要做一个事件: 查询数据库(依据id查问)失去外联的实体对象
- 失去实体对象后就能够通过反射来获取属性值了.
- 差异化信息包装.
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