数据比较器,对比数据前后变化细节
前言
在开发的过程中,有时候需要对数据进行比对,来判断是否发生变化。如果一个字段一个字段比较,就太麻烦了。所以通过整合注解与反射的方式,实现一个通用的实体数据比较框架。
设计
- 使用注解,确定需要比较的属性。
- 反射获取属性与数据内容。
- 循环比较数据内容,并写入到结果中。
- 提供多种比较入参
总体结构如下:
正文
1、定义注解
1) 实体注解,确定实体名称
不是基本类型是,必须要有该注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 功能描述: 属性实体标识 <br/>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE})
public @interface PropertyEntity {
/** 实体唯一标识 */
String value();
}
2) 主键注解,校验数据是否一致
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 功能描述: 唯一标记,可以又多个,用于联合索引 <br/>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface PropertyId {
}
3) 属性描述注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 功能描述: 属性描述 <br/>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD, ElementType.FIELD})
public @interface PropertyField {
/** 中文描述 */
String name() default "";
/** 排序字段,与@PropertyOrder可以同时使用,取两个最大的为主 */
float order() default 0.00F;
}
4) 顺序注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 功能描述: 属性排序 <br/>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD, ElementType.FIELD})
public @interface PropertyOrder {
/** 排序值 */
float value() default 0.00F;
}
5) 排除注解,不进行比较
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 功能描述: 属性忽略比较 <br/>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD, ElementType.FIELD})
public @interface PropertyIgnore {
}
6) 自定义比较器
如果有特殊比较方式,则自行定义比较器
import com.cah.project.compare.comparator.DefaultComparator;
import com.cah.project.compare.comparator.IComparator;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 功能描述: 属性比较器,可以自定义 <br/>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD, ElementType.FIELD})
public @interface PropertyComparator {
/** 比较器,默认比较器 */
Class<? extends IComparator> compare() default DefaultComparator.class;
}
2、自定义比较器
1) 比较器接口
/**
* 功能描述: 比较器接口 <br/>
*/
public interface IComparator<T> {
/**
* 功能描述: 对象比较 <br/>
*
* @param t1 对象1
* @param t2 对象2
* @return "int" 返回比较结果 0-相同;非0-不同
*/
int compare(T t1, T t2);
}
2) 默认比较器实现
import java.util.Date;
import java.util.Objects;
/**
* 功能描述: 默认比较器 <br/>
*/
public class DefaultComparator implements IComparator<Object> {
@Override
public int compare(Object o1, Object o2) {
// 同时为空,为相同
if(Objects.isNull(o1) && Objects.isNull(o2)) {
return 0;
}
// 都不为空
if(!Objects.isNull(o2) && !Objects.isNull(o1)) {
if(o1 instanceof Date) {
return ((Date) o1).compareTo((Date) o2);
} else {
if(o1 == o2 || o1.equals(o2)) {
return 0;
}
}
return -1;
}
return -1;
}
}
3、异常类
import com.cah.project.compare.enums.ExceptionEnum;
import lombok.AllArgsConstructor;
/**
* 功能描述: 比较异常类 <br/>
*/
@AllArgsConstructor
public class CompareException extends RuntimeException {
private final String code;
private final String desc;
public CompareException(ExceptionEnum ee) {
this(ee.getCode(), ee.getDesc());
}
public CompareException(ExceptionEnum ee, Object... args) {
this(ee.getCode(), String.format(ee.getDesc(), args));
}
}
4、枚举定义
1) 变化类型:新增,修改,删除,无变化等四种情况
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 功能描述: 变化类型枚举 <br/>
*/
@Getter
@AllArgsConstructor
public enum ChangeTypeEnum {
ADDED("1", "新增"),
REMOVED("2", "删除"),
MODIFIED("3", "修改"),
UNCHANGED("4", "无变化"),
;
private final String code;
private final String desc;
}
2) 模型类型枚举
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 功能描述: 模型类型 <br/>
*/
@Getter
@AllArgsConstructor
public enum ModelTypeEnum {
ENTITY("Entity", "实体"),
PROPERTY("Property", "基础属性"),
ENTITY_PROPERTY("EntityProperty", "实体属性"),
LIST_PROPERTY("ListProperty", "列表属性"),
MAP_PROPERTY("MapProperty", "Map属性"),
;
private final String code;
private final String desc;
}
3) 异常枚举
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 功能描述: 异常枚举 <br/>
*/
@Getter
@AllArgsConstructor
public enum ExceptionEnum {
OVER_DEPTH("0", "数据结构深度超过指定范围"),
INCONSISTENT_CLASS("1", "比较的对象类型不一致"),
PROPERTY_ENTITY_NULL("2", "比较的对象必须拥有@PropertyEntity注解"),
PROPERTY_ID_NULL("3", "比较的对象必须拥有@PropertyId注解"),
PROPERTY_ID_TYPE("4", "对象%s的@PropertyId注解类型必须为String或Long"),
PROPERTY_ID_VALUE_NULL("5", "对象%s属性%s的@PropertyId注解的值为空"),
;
private final String code;
private final String desc;
}
4) 实体类型枚举
这里使用了枚举+单例的模式。这里为什么不使用策略枚举的原因,在 AnalyzeUtil
中需要做属性类型的判断,不方便使用。
import com.cah.project.compare.process.IPropertyProcess;
import com.cah.project.compare.process.impl.BaseTypeProcess;
import com.cah.project.compare.process.impl.EntityTypeProcess;
import com.cah.project.compare.process.impl.ListTypeProcess;
import com.cah.project.compare.process.impl.MapTypeProcess;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
import java.util.Map;
/**
* 功能描述: 实体类型枚举 <br/>
*/
@Getter
@AllArgsConstructor
public enum PropertyTypeEnum {
BASE_TYPE("base", "基础数据类型(int/String/...)", new BaseTypeProcess()),
LIST_TYPE(List.class.getTypeName(), "List", new ListTypeProcess()),
MAP_TYPE(Map.class.getTypeName(), "Map", new MapTypeProcess()),
ENTITY_OBJECT_TYPE("entityObject", "自定义实体对象", new EntityTypeProcess()),
;
private final String typeName;
private final String desc;
// 处理器
private final IPropertyProcess process;
}
5、处理器,与实体类型枚举一起使用
1) 处理器接口
import com.cah.project.compare.enums.ChangeTypeEnum;
import com.cah.project.compare.model.ChangeModel;
import com.cah.project.compare.model.PropertyModel;
/**
* 功能描述: 实体过程 <br/>
*/
public interface IPropertyProcess {
/**
* 功能描述: 单个对象处理 <br/>
*
* @param pm 属性模型
* @param cte 变化类型
* @return "com.cah.project.compare.model.ChangeModel"
*/
ChangeModel process(PropertyModel pm, ChangeTypeEnum cte) throws InstantiationException, IllegalAccessException;
/**
* 功能描述: 两个对象处理 <br/>
*
* @param beforePm before属性模型
* @param afterPm after属性模型
* @return "com.cah.project.compare.model.ChangeModel"
*/
ChangeModel process(PropertyModel beforePm, PropertyModel afterPm) throws InstantiationException, IllegalAccessException;
}
2) 处理器抽象类
将共有的方法封装在这里,方便各个真实处理器继承使用
import com.cah.project.compare.enums.ChangeTypeEnum;
import com.cah.project.compare.enums.ModelTypeEnum;
import com.cah.project.compare.model.ChangeModel;
import com.cah.project.compare.model.PropertyModel;
import java.util.Objects;
public abstract class AbsProcess implements IPropertyProcess {
protected abstract ModelTypeEnum getModelType();
/**
* 功能描述: 获取基本的类型 <br/>
*
* @param pm 属性模型
* @param cte 变化类型
* @return "com.compare.model.ChangeModel"
*/
protected ChangeModel getBaseChangeModel(PropertyModel pm, ChangeTypeEnum cte) {
ChangeModel cm = getBaseChangeModel(pm);
cm.setChangeType(cte);
if(!Objects.isNull(pm.getValue())) {
cm.setTypeValue(pm.getValue().toString());
}
// 删除的,说明before有,after没有
cm.setBefore(ChangeTypeEnum.REMOVED.equals(cte) ? pm.getValue() : null);
// 新增的,说明after有,before没有
cm.setAfter(ChangeTypeEnum.ADDED.equals(cte) ? pm