数据迁移-数据验证程序
数据迁移-数据验证程序
目前有很多架构升级项目,例如服务升级新模型。初始化后没有一套统一的方案和标准对迁移前后的数据进行查错和验证。尤其有些数据可能从用户界面上不容易发现异常,或者数据量较多的场景下通过用户界面取样验证并不可靠。因此开发该程序用于数据验证。
目标:
验证数据初始化后新老模型数据:完整性、一致性和数据的质量。
流程图:
备注:
- 历史数据和新模型数据的ID从数据初始化记录表获取,此记录表记录了本次初始化的基本情况,至少包含如下三个字段:1、原始数据ID、2、新模型ID、3、初始化状态(待初始化、成功、失败)
程序实例
目前已完成一个基础版本的验证程序开发
程序目前用于校验询价服务初始化前后数据校验
验证流程:
程序说明
程序核心抽象类
数据验证程序模板类,此类定义了主要用于验证数据的抽象方法,具体的验证需要每个业务自定义实现其中的抽象方法:
/**
* 数据验证模板类
*
* @param <O> 旧模型校验对象泛型
* @param <N> 新模型校验对象泛型
* @author tuan_luo
*/
abstract class DataVerificationService<N, O> {
// 缺省具体抽象方法,主要抽象方法说明见下
}
获取历史数据抽象方法:
getHistoryDatas
获取新模型数据集合抽象方法:
getNewModelDatas
执行完整性校验抽象方法:
performIntegrityCheck
protected abstract void performIntegrityCheck(List<O> hisDatas, List<N> newData);
执行一致性校验抽象方法:
dataConsistencyCheck
protected abstract void dataConsistencyCheck(List<O> hisDatas, List<N> newData);
执行深度数据质量校验抽象方法:
dataQualityCheck
protected abstract void dataQualityCheck(List<O> hisDatas, List<N> newData);
询价服务实现类:
/**
* 询价服务数据初始化验证
*
* @author tuan_luo
* @date 2024年01月30日 18:08
*/
@Service
public class InquiryDeliveryDateVerificationService
extends DataVerificationService<InquiryDataInitVerifyVO, InquiryDataInitVerifyVO> {
}
注解驱动的数据质量验证器
使用反射获取字段上的校验规则注解,根据其进行字段校验。
@Service
public class CustomValidator<T, E> {
@SafeVarargs
public final String validate(T obj, E oldObj, CustomConstraintValidator<CustomMapped, E, T>... validator) {
Class<?> clazz = obj.getClass();
StringBuilder msg = new StringBuilder();
for (Field field : clazz.getDeclaredFields()) {
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof NotNullEmptyBlank) {
NotNullEmptyBlank notEmpty = (NotNullEmptyBlank) annotation;
// 具体实现缺省
}
}
}
}
}
自定义校验规则
如果是非常复杂的校验逻辑,不合适使用注解校验,则需要在字段上使用特殊注解:CustomMapped
以此标记该字段需要自定义校验。
自定义校验规则实现
使用 CustomMapped
标记字段后需要对应的实现一个自定义校验接口的bean,接口定义如下。
/**
* 属性特殊校验
*/
interface FieldValidator<T, E> {
void valid(T newValue, E oldValue);
}
附:目前的注解
@InIntEnums
说明:标记字段范围为固定的int值。比如
询价服务的服务处理状态dealStatus
在 (18,19,21,22) 范围内。
实例:
// 服务状态一定是这几个值
@NotNullEmptyBlank
@InIntEnums(intEnums = {18, 19, 21, 22})
@CustomMapped(newFieldName = "dealStatus", oldFieldName = "dealStatus", validatorIndex = 0)
private Integer dealStatus;
@InRange
说明:标记字段在某闭区间范围内
实例:
// 由初始化而来的ID一定是4亿-5亿的
@NotNullEmptyBlank
@InRange(min = 400000000, max = 500000000)
@CustomMapped(newFieldName = "id", oldFieldName = "id", validatorIndex = 0)
private Integer id;
@BothEqual
说明:标记字段和老模型 一样。
@NotNullEmptyBlank
说明:标记字段不为空,(obj不为null、list不为空、map不为空)
@Pattern
说明:标记字段需要符合正则表达式
@CustomMapped
--标记字段还需要进行自定义校验。
附录:本次询价服务适用注解校验的字段:
/**
* 询价询交期初始化后数据校验用-历史数据VO
*
* @author tuan_luo
*/
public class InquiryDataInitVerifyVO implements Serializable {
private static final long serialVersionUID = 3557529056592885636L;
@NotNullEmptyBlank
@InRange(min = 400000000, max = 500000000)
@CustomMapped(newFieldName = "id", oldFieldName = "id", validatorIndex = 0)
private Integer id;
@NotNullEmptyBlank
@InIntEnums(intEnums = {14, 15})
@BothEqual
//服务类别Id
private Integer categoryId;
//服务ID
@NotNullEmptyBlank
@BothEqual
private Integer serviceId;
//服务ID
@NotNullEmptyBlank
@InIntEnums(intEnums = {1})
private Integer sourceSite;
//公司名
private String enterpriseName;
//公司ID
private Integer enterpriseId;
//会员Id
@NotNullEmptyBlank
private Integer memId;
//交付状态: 0-进行中 1-中止 2-已完成
@BothEqual
private Integer deliveryStatus;
//服务商id
@NotNullEmptyBlank
@InIntEnums(intEnums = {1})
private Integer spId;
//服务商id
@NotNullEmptyBlank
@InIntEnums(intEnums = {0, 1, 2})
private Integer type;
//提交时间
@BothEqual
private Date submitTime;
@NotNullEmptyBlank
@InIntEnums(intEnums = {18, 19, 21, 22})
@CustomMapped(newFieldName = "dealStatus", oldFieldName = "dealStatus", validatorIndex = 0)
private Integer dealStatus;
/**
* 服务单号
*/
@BothEqual
@NotNullEmptyBlank
private String serviceOrderNumber;
//服务过程ON集合
@Sublist
@NotNullEmptyBlank
@CustomMapped(newFieldName = "processOnDOList", oldFieldName = "processOnDOList", validatorIndex = 0)
private List<ServiceProcessOnVO> processOnDOList;
}
备注:
自动验证后,功能用例的验证也是不可或缺的。