对象比拟
命题
- 对数据库对象在更新的时候进行数据比拟, 记录差别.
设计
确定比拟对象
- 在这里应用 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` 的实体对象
- 判断这个类是否存有
-
@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;}
}
- 比拟
- 比拟对象是雷同的类
这里间接通过一个泛型 T 解决 - 获取新老字段属性值, 比拟字段
反射遍历字段, 获取新老字段属性值 - 通过字段名称从上一步的失去的 map 中获取注解信息
key: 具备 `@HavingDiff` 注解的类.class
value:
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;
}
@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