-
2019-12-12 16:32:11
[目录]
- 第一章:概述
- 第二章:整体数据分层
- 第三章:整体实现框架
- 第四章:元数据
- 第五章:ETL
- 第六章:数据校验
- 第七章:数据标准化
- 第八章:去重
- 第九章:增量/全量
- 第十章:拉链处理
- 第十一章:分布式处理增量
- 第十二章:列式存储
- 第十三章:逻辑数据模型(数仓模型)
- 第十四章:数据模型参考
- 第十五章:维模型
- 第十六章:渐变维
- 第十七章:数据回滚
- 第十八章:关于报表
- 第十九章:数据挖掘
数据仓库实践杂谈(六)-数据校验
从数据源卸载出来的数据,进入仓库之前的第一个步骤就需要进行数据校验。数据校验的前提是在元数据中建立一套合适的数据标准。而其中,最重要的是确定每个字段的取值范围。基于这个数据标准,同步建立一套程序用于检查将要进入仓库的数据的有效性。
这套程序不但在运行阶段每日检查,在数据分析阶段也会用来做数据质量的检查。
数据的取值范围一般包括如下几种:
- 是否可空;
- 数值的取值范围:整数/小数点,取值起止范围等;
- 取值的枚举范围;
另外,还需要考虑数据是否有重复,乱码之类。对于从一些老旧系统导出的数据出现这个问题的可能性较大。
数据校验是最容易通过元数据驱动实现的模块。一般情况都应该利用元数据开发一套通用程序来处理。
数据校验的输出有以下三个:
- 验证正确的数据,进入加载处理(增量/全量)环节;
- 校验不通过的数据,进入到脏数据区,一般情况脏数据都需要人工介入处理;
- 生成数据质量报告,包含每个表、字段的空值率,有效率等。
数据校验类似一个过滤器,让符合条件的数据往下走,同时抓出不符合条件的数据。这里有一个核心的问题需要优先考虑:
一份数据文件里面存在脏数据之后,是否还继续往下处理?首先明确一点,大部分的数据问题,是系统无法自动解决的。只有类似FTP下来的数据文件不完整,可以去尝试重新下载之外,别的基本上没办法自动处理了。一般来说,可能会出现下列的错误类型:
- 网络错误:导致文件传输不完整、错位、乱码等;这类错误基本会导致文件无法解析,一般进行重试,或者直接报警,手工更新文件之后重新执行。
- 数据卸出系统错误:源系统卸出数据文件程序发生错误,导致文件不完整、错位、乱码甚至生成文件失败等,此类错误一般也会导致文件无法解析,需要立刻安排检查源系统程序重新生成文件。
- 数据错误:比如特定字段给了非法的值。这类错误往往是因为源系统业务流程或者数据校验不完善导致出现非法数据。然后,这种错误不是立马可以修复的。当然,在一个正常运作的系统,这类的错误往往只会占少数。
一般情况,有数据错误的文件,建议是把错误数据筛选出来之后,继续执行。停止一个文件执行对整个流程的伤害是非常大的,很可能整个流程都会停下来。毕竟,大部分业务数据文件,其中只会有一小部分数据有问题,只会影响部分业务的分析结果。
当然,如果发生系统性错误,经过自动重试仍失败,就需要停下流程,立即报警,马上处理。
重点考虑第三种问题。某个文件发现了部分不合规的数据,把违法数据单独保存起来,剩下的数据继续进行处理,进行增量处理以及后续相关的处理。正常数据的后续流程,从增量到建模到分析,后续章节会继续讨论。这里主要讨论异常数据怎么处理。
一般情况,或者说几乎所有的情况了,异常数据都是需要人工介入处理的。一般处理流程:
- 检查异常数据,对比源系统数据;
- 追溯业务起源,确定正确内容;
- 最后的选择:
- 在源系统修复数据重新导出;
- 在专门的界面修复异常数据;
- 直接无视。
数据的修复,别指望当天可以完成。一般都得好几天。源系统修复和升级,更不是短时间内能做到了。如果在确定不影响业务的情况下,可以把异常数据给一个专门的类别,比如某个分类字段,业务限定只能有01、02和03三个值。现在来的数据发现一个06。可以保留这个06。但如果这个字段存在层次汇总的话,需要设置成一个专门的类别来表示其他的值,比如99。把异常的数据都汇总到99上。
数仓平台一般都提供一个错误数据修复(补录)的界面。首先把不合法的数据,装到单独的数据库里面(便于检索和修改),然后提供一个界面,展示每一条违法数据。这个界面,严格按照元数据的定义来校验数据,避免造成二次错误。修复数据之后,重新生成一个数据文件/表重新进行数据处理。假设这个数据为D,只有T日处理中校验出来的违法数据,其他合法数据已经处理到仓里里面去了。
按如下策略执行:
- 重新进行数据校验,尤其是源系统重新导出的数据;
- 重新执行增量处理,但需要把处理日期设置为T,同时关闭对删除
- 数据的判断(也就是和全量文件对比,缺少的数据忽略即可,不认为是源系统删除了此数据);
- 增量式处理数仓模型数据(如果有的话);
- 维模型(星型模型)表,一般仓库的基础表只会保存最明细粒度的事实表,需要重新计算当日的事实表数据。
- 之后高层次的汇总分析集市部分,简单粗暴的,干掉重新生成吧。
如果是通过源系统自行修复之后重新导出数据的话,则有两种情况:
- 导出当日全天的数据,需要先行用保存下来的异常数据对比一下,把修复好的异常数据单独拿出来执行上面的流程;
- 如果导出的只有修复之后的错误数据,则直接执行上面的流程。
基于明细的表,包括全量历史表和数仓模型表,以及最明细粒度的维模型事实表,其结构都是支持增量叠加和修改某一天的数据。所以只需要确定处理的数据日期,重新执行一般来说问题不大的。但再往后,比如分析集市,其数据经过汇总的,就不一定很好进行处理了。但类似分析集市一类的数据,往往都不会单独保存在数据库或者自己设计的数据结构中了,都会使用专用工具,比如Cognos之类,不但数据需要汇总,还会压缩等。所以这部分数据,基本就是直接重建了。
需要注意的是,这里说的异常数据,主要是流水表,订单表这种业务发生记录的数据。如果是机构表、地区表之类的数据有错误,一方面一般程序自动校验不出来,另一方面涉及到统计维度,造成的问题会很严重。
所以:
- 数据尽量不要错;
- 仓库提供回滚机制,能恢复到一定期间内指定日期的样子。
未完待续。
更多相关内容 -
循环冗余校验在数据通信中的应用
2021-01-20 03:52:18在工业环境中,电子系统通常工作在极端的温度条件下,或处 于电子噪声环境,或是其它恶劣条件,而系统在这种条件下能 否正常工作至关重要。举例来说,如果发送给控制机器臂位置 的DAC 的数据遭到破坏,机器臂就会按... -
模拟技术中的循环冗余校验在数据通信中的应用
2020-10-22 17:26:20在工业环境中,电子系统通常工作在极端的温度条件下,或处 于电子噪声环境,或是其它恶劣条件,而系统在这种条件下能 否正常工作至关重要。举例来说,如果发送给控制机器臂位置 的DAC 的数据遭到破坏,机器臂就会按... -
Kettle使用_7 数据校验组件应用
2021-01-18 18:45:53Kettle使用_8 数据校验组件应用 需求: 通过数据校验组件完成数据字段、长度、类型、字段是否为空、字段是否在指定的范围内的检验 统计出错误的个数(错误类型的个数) 能根据错误个数做流程终止的控制。 ...Kettle使用_7 数据校验组件应用
需求:
- 通过数据校验组件完成数据字段、长度、类型、字段是否为空、字段是否在指定的范围内的检验
- 统计出错误的个数(或仅按照错误类型的个数)
- 能根据错误个数做流程终止的控制。
解决方法:
通过数据校验组件结合分组统计、过滤记录和终止流程组件来完成。
- 场景一:字段最大长度检验
Step1: 拖动一个自定义常量组件(字段是name,类型是string,数据见下图)到转换并
Step2:将转换里检验分类下的数据检验拖到转换里。
Step3: 按住SHIFT,从自定义常量里连接到数据校验组件。
Step4:设置最大字符串长度为8
- 场景二:检查数字类型最大长度(这里检查score最大值为70)
步骤类似场景一,这里的自定义常量数据见下:
数据校验配置数据最大值配置:
- 场景三:检查数据是否为空(这里code字段第二行为空)
这里的自定义常量数据见下:
数据校验,是否为空配置。
- 场景四:字段类型检验(这里给的CityCode是integer,但检验时需要是String类型)
数据检验里数据类型检查配置
- 场景五:数据是否属于某个范围(List)
自定义数据如下(工资水平有A、B、C、D、E、F6条记录,而实际合法的是如下list :A、B、C、D)
数据校验(在一个范围内)的配置。
- 场景六:综合案例(即需求部分)
配置如上的数据校验组件与统计组件互连到一个Dummy组件
这里统计组件可按照如下设置:
可设置过滤组件如下:
如果满足过滤条件,则进入流程终止。
完整流程示意:
-
使用 FluentValidation 实现数据校验、验重
2019-05-18 12:27:46最近项目里用到了 FluentValidation 对网站用户输入的数据进行了验证,使用起来比较舒服,下面整理一下项目中集成的过程。 需要集成的项目是一个 asp.net core 2.1 版本的项目。第一步,安装 FluentValidation....最近项目里用到了 FluentValidation 对网站用户输入的数据进行了验证,使用起来比较舒服,下面整理一下项目中集成的过程。
需要集成的项目是一个 asp.net core 2.1 版本的项目。第一步,安装 FluentValidation.AspNetCore,VS会自动安装依赖的 FluentValidation、DI 等包。安装完成后,找到你要验证的数据类,比如我这里是一个修改密码的场景,类名是 UserPassword:
public class UserPassword { /// <summary> /// 用户名 /// </summary> public string UserName { get; set; } /// <summary> /// 旧密码 /// </summary> public string OldPassword { get; set; } /// <summary> /// 新密码 /// </summary> public string NewPassword { get; set; } /// <summary> /// 重复密码 /// </summary> public string NewPasswordRe { get; set; } }
找到类后,为这个类写验证规则类,需要继承 AbstractValidator<UserPassword> 泛型类,其中 UserPassword 泛型是你要验证的类。在验证类的构造方法里写验证规则,代码如下:
public class UserPasswordValid : AbstractValidator<UserPassword> { public UserPasswordValid() { CascadeMode = CascadeMode.StopOnFirstFailure; RuleFor(x => x.UserName).NotNull().WithName("用户名"); RuleFor(x => x.OldPassword).NotEmpty().Length(4, 32).WithMessage("旧密码不能为空且长度必须符合规则"); RuleFor(x => x.NewPassword).NotEmpty().Length(4, 32).WithMessage("新密码不能为空且长度必须符合规则") .Must(NewNotEqualsOld).WithMessage("新密码不能跟旧密码一样"); RuleFor(x => x.NewPasswordRe).NotEmpty().WithMessage("重复密码不能为空").Must(ReEqualsNew).WithMessage("重复密码必须跟新密码一样"); } /// <summary> /// 判断新旧密码是否一样 /// </summary> /// <param name="model">实体对象</param> /// <param name="newPwd">新密码</param> /// <returns>结果</returns> private bool NewNotEqualsOld(UserPassword model, string newPwd) { return model.OldPassword != newPwd; } /// <summary> /// 判断新密码与重复密码是否一样 /// </summary> /// <param name="model"></param> /// <param name="newPwdRe"></param> /// <returns></returns> private bool ReEqualsNew(UserPassword model, string newPwdRe) { return model.NewPassword == newPwdRe; } }
FluentValidation 的语法很人性化,初次接触的人大概都能看懂。如 RuleFor(x => x.UserName).NotNull().WithName("用户名");
这句就是 UserName 这个字段不能为 null ,为 null 时,会报 “用户名”不应为 null,WithName 就是给字段指定一个名字。
NotEmpty 就是不能为空,包括 null ;WithMessage 就是不符合条件时的提示。编写完验证规则,要在 StartUp 里注册一下。在 services.AddMvc 这个子句的后边(注意还是在这句中)加入AddFluentValidation。
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2) .AddFluentValidation(cfg => { cfg.RegisterValidatorsFromAssemblyContaining<UserPasswordValid>(); cfg.RunDefaultMvcValidationAfterFluentValidationExecutes = false; });
其中 cfg.RegisterValidatorsFromAssemblyContaining<UserPasswordValid>(); 这句注册你的验证类,如果有多个,就每个都要注册。cfg.RunDefaultMvcValidationAfterFluentValidationExecutes = false; 这句是告诉程序,用 FluentValidation 验证完,不要再使用 Mvc 的验证了。
最后就是在控制器里写处理错误的代码了,FluentValidation 会把验证的结果写入 ModelState,我们拿 ModelState 来验证就可以。
public IActionResult Index([FromBody]UserPassword userPassword) { if (!ModelState.IsValid) { return Json(new { Success = false, Item = ModelState.GetErrors() }); } else { return Json(new { Success = true }); } }
其中 ModelState.GetErrors() 是我写的一个扩展方法,为了把错误返回给前端,代码如下:
public static class ExtMethods { public static List<ValidationError> GetErrors(this Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary ModelState) { var errors = new List<ValidationError>(); foreach (var pair in ModelState) { foreach (var error in pair.Value.Errors) { errors.Add(new ValidationError(pair.Key, error.ErrorMessage)); } } return errors; } public static string ToSingleString(this IEnumerable<ValidationError> validations) { return validations.Select(x => x.Message).ToStringDot(); } }
/// <summary> /// 数据验证错误 /// </summary> public class ValidationError { public ValidationError(string name, string message) { this.Name = name; this.Message = message; } public string Name { get; set; } public string Message { get; set; } }
代码中的 ToStringDot 方法是我写的扩展方法,功能和 string.Join 一样。我在前端使用的 element-ui 作为前端展示库,效果如下:
对于数据的基本验证这样就可以,但是我们还有一些数据验证逻辑是需要走数据库的,这时就需要我们把验证的方法告诉 FluentValidation。比如在另一个场景中,我需要验证当前数据和数据库现有数据是否重复,验证的方法在一个叫 RoleService 的服务里, RoleService 实现了 IRoleService 接口,并且使用 aspnetcore 自带的微软DI进行注入。 那么我们需要在 Validator 的构造方法里把 Service 注入进来并在 Must 方法里使用这个 Service 进行验证。
/// <summary> /// 角色创建的验证器 /// </summary> public class RoleEditDtoValidator : AbstractValidator<RoleEditDto> { public RoleEditDtoValidator(IRoleService roleService) { RuleFor(x => x.RoleId).NotEmpty().WithName("角色编码"); RuleFor(x => x.RoleName).NotEmpty().WithName("角色名称"); RuleFor(x => x).Must(x => roleService.ExistCheck(x.Model)) .When(x => !x.RoleId.NullOrEmpty()) .WithMessage("数据存在重复,请检查!"); } }
然而这样在提示时,不知道提交的数据与数据库里哪条数据发生了重复,所以在提示里要把ID带出来。这个想了半天才想出办法来,代码如下:
/// <summary> /// 角色创建的验证器 /// </summary> public class RoleEditDtoValidator : AbstractValidator<RoleEditDto> { public RoleEditDtoValidator(IRoleService roleService) { RuleFor(x => x.RoleId).NotEmpty().WithName("角色编码"); RuleFor(x => x.RoleName).NotEmpty().WithName("角色名称"); RuleFor(x => x.WithExistId()).Must(x => { var rst = roleService.ExistCheck(x.Model, out var existId); x.ExistId = existId; return !rst; }) .When(x => !x.RoleId.NullOrEmpty()) .WithMessage((x, y) => "与 ID 为 " + y.ExistId.ToStringBy("、") + " 的数据存在重复,请检查!"); } }
其中 NullOrEmpty 是我写的一个扩展方法,功能和 string.IsNullOrEmpty 一样, ToStringBy 也是我的扩展方法,和 string.Join 功能一样;这两个扩展方法在 xLiAd.ExtMethods 的包里有,有兴趣的童鞋可以 Nuget 一下。WithExistId 方法是生成一个新的类,功能就是把数据库返回的重复 ID 给带上。代码如下:
public class ModelWithExistId<T> where T : class { readonly T t; public ModelWithExistId(T t) { this.t = t; } public T Model => t; public IEnumerable<int> ExistId { get; set; } } public static class ExtMethods { public static ModelWithExistId<T> WithExistId<T>(this T model) where T : class { return new ModelWithExistId<T>(model); } }
前台提示如下
OK了,这样就差不多了,完美实现需求。
需要注意的是,当你传的参数不符合 MODEL 的规范时 ModelState.IsValid 也会为假,比如你接收 类中的一个字段是 int 型,但你传来个空字符串,ModelState.IsValid 就会报值无效。 但是这个无效和 FluentValidation 是没有关系的,这一点容易使人产生困惑,要注意一下。
-
Java应用中的数据校验
2018-10-23 18:11:28[译者注:这篇文章是开源项目CUBA Platform的作者,在这篇文章中,作者阐述了CUBA平台中关于数据校验的设计思想和使用方式,可以作为大家在设计数据校验方面一个比较好的参考。] 我接触到的很多项目中,对数据校验...翻译:吴嘉俊 ,叩丁狼高级讲师。
[译者注:这篇文章是开源项目CUBA Platform的作者,在这篇文章中,作者阐述了CUBA平台中关于数据校验的设计思想和使用方式,可以作为大家在设计数据校验方面一个比较好的参考。]
我接触到的很多项目中,对数据校验这方面内容都没有一个很明确的策略。这些团队常常面对即将临近的交付期压力,不明确的项目续期,所以根本没有太多时间来规划和实现项目中的校验策略。所以,你可以看到,数据校验的代码零散的分布在整个应用中:Javascript中有,Java控制器中有,业务逻辑代码中有,实体模型中有,数据库中还有约束和触发器。用于数据校验的代码,充斥着各种if..else..,在不同位置抛出完全混乱的异常,甚至想找到一个数据究竟在哪里验证的,心里面都想骂一句FUCK。长此以往,当项目越来越复杂,验证会越来越难控制,陷入极为难堪的维护境地。
那么,是否有一种优雅的,标准的,简单的方法来处理应用中的数据校验呢?这个方法既让我们的代码可读性较高,又能将大部分的数据校验代码集中管理,还能很好的集成进入目前主流的Java框架呢?
是的,有这种方法。
我们开发了CUBA Platform(https://www.cuba-platform.com/),能让我们按照最佳实践的方式来完成。我们归纳除了关于校验代码的一些要求:
- 能重复使用,遵循DRY原则;
- 能自然清晰的表达验证规则;
- 放在程序员愿意放置的位置;
- 能够支持从不同的数据源中获取数据,比如用户输入,SOAP或者REST请求等;
- 支持并发处理;
- 应用隐式的去调用,而不需要处处都通过手动调用;
- 展示清晰,本地化的提示信息供开发者使用;
- 遵循业界已有标准;
在这篇文章中,我们会使用一个基于CUBA平台的应用来作为示例。CUBA是基于Spring和EclipseLink平台的,所以,文中大部分的例子也能在其他支持JPA和Java bean校验的标准平台上面执行。
数据库约束校验
最普遍,最直接的数据校验可能就是使用数据库级别的约束,比如Require(或者NOT NULL),字符串长度,唯一索引等等,特别在企业应用中,大部分以数据库为中心,这是非常常见的方法。但是,因为开发人员分工职责不同,常常在不同的应用层中,重复定义数据约束,这就非常容易出错。
我们来举一个例子,我们大多数开发都见过或者参与过。如果在需求中提出,护照这个字段需要10位数字,那么,最可能的情况回事这样:DB工程师,会在DDL中限制,后台开发会在实体和REST服务中检查,最后,UI层开发会在前端(客户端)限制。好了,过了一段时间,需求修改了,护照字段改成15位了,技术修改了数据库中的定义,但客户端仍然只能输入10位数据。
所有人都知道如何避免出现这个问题,那就是数据校验必须集中!在CUBA平台中,这个集中点就是在实体类中使用JPA注解。基于这些元数据信息,CUBA平台能够生成正确的DDL,并且在客户端生成正确的校验器,如下图所示。
如果JPA注解发生了变化,CUBA会及时生成修改补丁脚本,下一次再部署应用的时候,基于新的JPA限制会在DB和UI端更新。
为了隔离数据库的复杂性,让生成的DDL脚本保持标准,避免引入数据库相关的独特的触发器或者存储过程,JPA注解仅仅只能做一些最基本的校验,比如,保证实体字段的唯一性,必要性,或者定义字符串长度,此外,还可以使用@UniqueConstraint注解来完成复合唯一约束等,但这些仍远远不够。
在需要更加复杂的验证逻辑,比如检查字段最大最小值,或者正则表达式验证,或者完成一个特殊的数据校验,我们就需要使用Bean Validation。
Bean校验
我们知道,遵循规范是一个最佳实践。规范是根据成千上万的应用归纳和验证的,在Java Bean验证这块,现有的规范主要是JSR 380,JSR 349和JSR 303(https://beanvalidation.org/specification/),最出名的实现就是Hibernate Validator(https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/?v=5.3)和Apache BVal(http://bval.apache.org/)。
尽管不少开发都熟悉这套工具,但是它的带来的好处却经常被误解。这是一种为遗留项目添加数据验证的有效的方法,允许你以清晰、直接,可靠的方式,以尽可能接近业务逻辑的方式表达你的验证规则。使用Bean验证能为你的应用带来很多好处:
- 验证逻辑尽可能的靠近领域模型,在模型中定义值,方法,bean约束是很符合OOP方法的。
- Bean验证标准提供了多种直接可以使用的验证规则(@NotNull"">https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#validator-defineconstraints-spec),比如:@NotNull, @Size, @Min, @Max,@Pattern, @Email, @Past,@URL, @Length,@ScriptAssert等等;
- 允许扩展已有的约束,或者定义你自己的约束注解。你把多个其他验证约束组合起来变成一个新的校验注解,或者通过开发一个校验器,创建一个全新的校验注解。
- 举个例子,回到我们之前的护照的例子,我们完全可以创建一个类级别的注解:@ValidPassportNumber,用这个注解根据我们的国家(country)字段来校验我们的护照号码字段。
- 你不仅仅能够在类或者字段上面增加约束,还能够在方法和方法参数上添加约束,这种方法叫做“合约约束(validation by contract)”,这是下一节介绍的重点。
CUBA平台(或者一些其他平台)在用户提交数据的时候,会自动的执行这些验证规则,如果验证失败,用户立刻会得到错误的提示,这一些都是自动运行的,不需要手动干预或者调用。
让我们再来看看护照号码的例子,我们这次会增加一些额外的约束:
- 用户名至少2位及以上,并且是合法的名字。这个正则式比较复杂,因为R2D2这个名字无效,但是Charles Ogier de Batz de Castelmore Comte d’Artagnan却是一个有效的名字。
- 用户的身高应该在0到300厘米之间;
- Email必须是一个正确的email地址;
那么,现在的Person类应该类似这样:
我想@NotNull, @DecimalMin, @Length, @Pattern这些标准的注解的用法,大家应该很熟悉,就不用做过多说明,下面来看看@ValidPassportNumber注解是如何实现的。
我们的@ValidPassportNumber注解使用Person#country配合正则表达式来检查Person#passportNumber。
首先,根据文档(CUBA或者Hibernate文档),我们使用@ValidPassportNumber注解标记我们的类,并制定分组参数(groups),UiCrossFieldChecks.class参数表示passportNumber的检查应该在每一个独立的字段检查完成之后再执行检查(所以UiCrossFieldChecks.class放在Default.class之后);
该注解的定义如下:
@Target指定该注解的标记位置;@Constraint(validatedBy = … )就是真正用于执行这个检查的检查类。这个ValidPassportNumberValidator类需要实现ConstraintValidator<…>接口,并且实现接口中定义的isValid(…)方法来完成真正的验证。
在CUBA平台中,除了需要创建我们自定义的校验器,给出错误的提示,除此之外,不需要写其他额外的代码,保证了代码的清晰和简洁。
来看看最后的工作效果:CUBA平台生成的前端脚本,能够展示错误提示,还能够对错误字段进行样式标记:
整个过程非常干净。我们只需要在业务模型上添加一些注解,就能得到一个漂亮的UI检查界面。
总结这个小节,使用bean 验证的主要好处有:
- 清晰,可读性高;
- 允许直接在业务类中定义值验证约束;
- 易扩展和自定义;
- 很多流行的ORM框架支持自动检查,并支持DDL同步更新;
- 一些框架支持自动生成验证UI脚本,自动调用验证(如果不支持和,通过Validator接口手动验证,也是很容易的)
- bean校验遵循业界的规范,有非常多的相关文档和社区支持。
那么,如果需要在方法,构造器,甚至一个被外部系统调用传入数据的REST端点上进行验证,该怎么做呢?又或者我们想检查方法的参数值,但是又不想写一堆无聊的if-else代码,又应该怎么做呢?
答案很简单,可以在方法上也使用bean验证;
合约约束
有时候,我们不仅仅只是验证应用数据模型的值,我们希望更进一步,如果使方法也能够受益于传入参数和返回值的自动验证。这代表,不仅仅能够检查从REST或者SOAP端传入的数据,还能表达方法执行的先决条件或者后决条件,或者要求返回参数在我们期望的范围之内,并且提供一定的可读性。
有了Bean验证,约束同样也可以施加在方法参数,或者方法的返回值上,或者构造方法,或者方法的先决条件或者后决条件判定上。相对于传统的参数和返回值校验,这种方式的好处有:
- 不需要手动执行验证(比如手动抛出IllegalArgumentException异常)。我们只需要声明我们的验证规则,这样我们代码会非常清晰和易读。
- 约束是可以重用的,支持灵活配置和扩展的:我们不需要每次要验证的地方都去写同样的代码:越少的代码,越少的bug。
- 如果一个类或者一个方法的返回值,或者一个方法的参数被标记上@Validated注解,那么每次方法的调用都会自动触发验证规则的执行。
- 如果使用@Documented标签,那么一个方法的先决或者后决条件会自动的包含在生成的JavaDoc中。
使用合约约束的结果就是,我们能得到干净,易于阅读和理解的代码。
下面我们来看看在CUBA应用中的一个REST控制器如何使用合约约束。PersonApiService接口允许使用getPersons()方法从数据库中获取用户列表,同时也提供了addNewPerson(…)接口添加一个用户。记住:bean验证是可以继承的,意味着,如果一个类或者字段使用了约束注解,那么所有子类或者实现了该接口的类都能得到同样的验证约束。
这个代码片段是不是看着非常清晰和易读?(@RequiredView(“_local”)可能比较碍眼,这是CUBA提供的注解,用于检查所有返回的Person对象的字段完全从PASSPORTNUMBER_PERSON表加载完成)。@Valid注解表明,通过getPersons()方法返回的每一个Person对象都需要被Person类上定义的约束检查。
CUBA将这些方法暴露成以下的服务:
- /app/rest/v2/services/passportnumber_PersonApiService/getPersons
- /app/rest/v2/services/passportnumber_PersonApiService/addNewPerson
我们使用POSTMAN来验证一下校验约束是否起作用:
你可能会注意到,上面的代码并没有验证护照号码。这是因为这个验证需要cross-parameter验证,passport的验证需要依赖于country值,所以这个验证会在实体类上进行验证(具体执行保存实体对象的时候验证)
Cross-parameter验证在JSR349和JSR380中已经支持。可以参考相关文档去看看如何在类/接口中的方法上实现Cross-parameter验证。
补充一些
没有什么东西是完美的,bean validation仍然有它的局限性:
- 可能有这样的需求,在每次对象状态发生变化时,都需要执行一个非常复杂验证。比如,你希望在你的电子商务系统中,检查客户的订单明细和你的集装箱一一匹配。这是一个非常“重”的操作,在每次用户添加一个订单项之前都要做这样一次检查,绝对不是一个好的想法。实际上,更好的做法是当所有的订单项都准备好之后,在保存到数据库之前,统一检查一次就可以了。
- 有的检查需要运行在事务中。比如,在电子商务系统中,需要检查订单数量在仓库中是否充足。这种检查必须要在一个事务中执行,因为库存中的数量可能在任何时间被其他事务同步修改。
CUBA平台提供了两种机制来对应这两种情况,一个叫做实体监听器(entity listener),一个叫做事务监听器(transcation listener)。我们来看看这两种机制的作用。
实体监听器(Entity Listeners)
CUBA提供的实体监听器类似JPA提供给开发的PreInsertEvent,PreUpdateEvent和PreDeleteEvent三种监听器。两种机制都允许在实体对象持久化到数据库之前或者之后进行验证。
在CUBA中定义并注册一个实体监听器也比较简单,我们只需要做如下两个事情:- 按照需要实现一个实体监听器接口。提供了3种不同目标的接口:BeforeDeleteEntityListener,BeforeInsertEntityListenerand和BeforeUpdateEntityListener
- 使用@Listeners注解将监听器绑定在需要检查的实体对象上。
CUBA平台和JPA标准(JSR338)有点区别的地方在于,CUBA提供的实体监听器接口是泛型的,所以你不需要再强制把Object类型参数强制转化成你需要检查的实体类型。CUBA会将和当前对象有关联的其他对象加载出来,或者可以使用EntityManager去加载或修改其他对象。所有这些对象的变化都可能会触发实体监听器的执行。
同时,CUBA平台提供了逻辑删除(soft deletion),即在数据库中并不真实删除对象,仅仅只是标记为删除。针对逻辑删除,CUBA平台会调用BeforeDeleteEntityListener/AfterDeleteEntityListener代替标准的修改触发PreUpdate/PostUpdate监听器。
我们来看一个例子。实体监听器使用@Listeners注解和一个实体类型绑定在一起,只需要在@Listeners中提供监听器的名字:
一个实体监听器可能的实现如下:
实体监听器非常适合:
- 在一个事务中,当实体对象持久化到数据库前检查数据;
- 在检查的过程中,需要从数据库中提取一些数据辅助检查;
- 检查数据需要依赖当前对象的关联对象,比如检查Order对象的时候,需要参考OrderItems;
- 追踪某一些实体对象的insert/update/delete操作。比如仅仅只是想追踪Order和OrderItem两个实体对象的数据库操作。
CUBA事务监听器运行在事务上下文中,与实体监听器不一样,事务监听器在每一次数据库事务调用的时候被启动。因为伴随着事务,所以你需要注意:
- 更加难以编码,
- 如果施加了过多无效的检查,性能会显著降低,
- 编码必须更加小心:在事务监听器中的一个bug可能会导致应用崩溃
事务监听器适用于需要用同样的算法检查多种类型的实体类的情况,比如用于检查所有业务数据的防欺诈检查。
我们来看一个事务监听器,该监听器检查所有标记了@FraudDetectionFlag注解的实体类,并使用自定义的防欺诈检查器检查。再次提醒,因为事务检查器是在每一次数据库事务提交之前进行检查,所以我们应该尽量减少用于检查的对象。
一个事务监听器必须实现BeforeCommitTransactionListener接口,并重写beforeCommit方法。事务监听器会在应用启动的时候启动。CUBA会自动将所有类型为BeforeCommitTransactionListener和AfterCompleteTransactionListener的类作为事务监听器。
小结
在一个企业项目中,Bean validation(JPA 303, 349 and 980)足以处理95%以上的数据验证情况。这种方式带来的最大的好处是基本上可以将所有验证集中在实体类上。所以验证规则集中,易读并且符合标准。Spring,CUBA以及其他很多代码库会在UI输入,验证方法调用或者ORM持久化处理过程中自动的调用验证检查,而不需要开发人员过度的关注。
在最后,我们在总结一下不同的验证方式针对的最佳的使用场景:- JPA validation:功能有限,适合简单的实体类型约束,并且易于将这些约束同步到DDL。
- Bean Validation:在领域模型类中,是最灵活,集中,复用性高,易读的验证方法。如果在事务之外执行验证,这是最该值得考虑的方法。
- Validation by Contract :基于bean验证,可以施加于方法的调用。当你想检查方法的输入参数或者返回值的时候,是非常值得考虑的方法,比如在一个REST请求处理中。
- Entity listeners: 虽然比不上Bean Validation的声明式校验,但是在一个数据库事务中需要检查对象以及关联对象的时候,是非常好的一个办法。比如需要从数据库中查询一些数据来支持验证规则。
- Transaction listeners :非常危险,但威力强大。运行在事务上下文中,当你需要在运行时确定需要检查哪些对象,或者当你需要使用相同规则验证多种类型对象的时候,这是一种值得考虑的验证方法。
我希望这篇文章能够刷新你对企业应用中如何使用不同的验证方法的看法,并给你一些关于如何改进正在进行的项目的验证相关架构的参考。
原文地址:https://www.javacodegeeks.com/2018/10/validation-java-applications.html
-
【SpringMVC学习06】SpringMVC中的数据校验
2016-06-22 20:30:27在实际中,通常使用较多是前端的校验,比如页面中js校验,对于安全要求较高的建议在服务端也要进行校验。服务端校验可以是在控制层conroller,也可以是在业务层service,controller校验页面请求的参数的合法性,在... -
ssm数据校验和数据回显
2018-11-05 12:50:07数据校验 校验的理解: 项目中,通常使用较多是前端的校验,比如页面中js校验。对于安全要求较高点建议在服务端进行... 业务层service(使用较多):主要校验关键业务参数,仅限于service接口中使用的参数。 ... -
SpringBoot 使用validation数据校验之分组校验怎么玩?·分组还有这么多讲究 - 第408篇
2021-12-20 16:37:22相关历史文章(阅读本文前,您可能需要先看下之前的系列????) 国内最全的SpringBoot系列之四 ...SpringBoot 使用validation数据校验-超级详细超级多干货 - 第406篇 悟纤:师傅,脑瓜疼? .. -
常用的数据校验方法
2018-06-11 16:26:571.什么是数据校验 通俗的说,就是为保证数据的完整性,用一种指定的算法对原始数据计算出的一个校验值。接收方用同样的算法计算一次校验值,如果和随数据提供的校验值一样,就说明数据是完整的。 2.最... -
elementUI表单验证的怪异问题-填入符合要求的数据校验不通过
2019-03-26 14:24:16两个问题都是由于数据格式不匹配校验未通过,在select中,可以强制将option的value改为string类型,解决问题。但在input的例子中,我们需要的必须是number类型。在代码中强制转换为number类型,... -
SpringBoot 使用validation数据校验-超级详细 - 第406篇
2021-12-13 21:45:16相关历史文章(阅读本文前,您可能需要先看下之前的系列????) 国内最全的SpringBoot系列之四 ...Spring Boot 项目中的 parent原来还有这么多的讲究 - 第398篇 Spring Boot条件注解 - 第400篇 SpringBoo.. -
@valid数据校验注解的使用
2019-03-24 17:22:00参数校验是我们程序开发中必不可少的过程。用户在前端页面上填写表单时,前端js程序会校验参数的合法性,当数据到了后端,为了防止恶意操作,保持程序的健壮性,后端同样需要对数据进行校验。后端参数校验最简单的... -
vue通过条件获取后台对应数据显示在表格
2019-05-23 14:01:29正确代码:(注:要想查看后台数据可以点击 https://www.easy-mock.com/mock/5ce57090f2fc446b5d8ffe18/blog/blogdata这个链接) < template> < div class=“show-examples”> < el-input p... -
在Kettle里使用参照表进行数据校验(流查询实现)
2016-12-26 16:10:19参照表一个常见的用途就是做...完整的转换如下图: 首先,需要一些输入数据,本例使用了“自定义常量数据”步骤,并添加一些测试数据作为输入,如下图: 第一个清洗步骤就是从邮政编码里提取数字,要使用计算器步骤。 -
校验实体对象中所有属性都不能为空
2019-07-11 16:46:55private String checkDTO(Object object){ if (null == object) { return "必填字段不能为空"; } try { //循环遍历对象属性 for(Field f : object.getClass().getDeclaredFields()){ ... -
vue ElementUI 表单验证 不能绑定多层次数据
2018-04-18 23:09:55elementUI 的 el-row组件提供了rules属性来定制 各个el-form-item的验证规则代码如下,其中form的 :model必须绑定一个对象,然后form-item prop属性绑定该对象下面的想要验证的属性,注意!!! prop指定的属性只能是... -
Java中使用注解校验参数
2021-02-09 17:30:16Java中参数校验的注解来自三方面,分别是 javax.validation:validation-api,对应包javax.validation.constraints org.springframework:spring-context,对应包org.springframework.validation org.hibernate:... -
Oracle Client安装报错:引用数据不可用于验证此操作系统分发的先决条件
2017-07-13 15:31:31原因是Oracle Client 11g版本不支持最新的Win10系统。 解决方案: 打开Oracle Client 11g安装包目录:\client\stage\cvu编辑该目录下的两个xml文件:oracle.client_InstantClient.xml和cvu_prereq.xml ... -
Java数据校验详解
2018-08-21 16:54:23...各路大神当然也会注意到这个问题,所以在“元编程”(见JSR250与资源控制)提出之后相续提交了JSR-303、JSR-349以及JSR-380来完善使用注解进行数据校验的机制,这三个JSR也被称为Bean Valid... -
java使用注解校验对象属性值数据长度
2021-03-08 08:09:43java使用注解校验对象属性值数据长度定义注解import java.lang.annotation.*;/*** describe:定义注解* current user Maochao.zhu* current system 2020/11/27*/@Target({ ElementType.FIELD, ElementType.TYPE })@... -
在kettle中实现数据验证和检查
2014-09-17 13:22:19利用kettle对输入数据进行验证检查,并处理跟踪错误数据。 -
在DTO中使用注解的形式检验数据是否为空,为什么这么做
2019-04-18 10:57:15如上图,在一个DTO中使用了注解的形式进行参数的非空验证,为什么要这么做呢? 为什么不直接在使用时对参数进行非空验证呢? 首先说一下DTO的作用,表现层与应用层之间是通过数据传输对象(DTO)进行交互的,数据... -
Struts2数据校验功能实现
2018-01-09 16:27:34前言数据校验一般是校验...回顾之前的数据验证,主要是在业务逻辑代码之前增加if条件判断来进行校验的。比如:在登录时候在Action中的login()方法中进行判断,但是我们只想在login()写业务逻辑,不想搞这么复杂的代码, -
SpringBoot 使用validation数据校验之自定义校验注解·源码分析+实例 - 第410篇
2021-12-27 18:04:43我们会发现有些场景下,上面提供的注解不能满足我们多样化的需求,那这种情况下,要怎么办呢?那我们会想validation框架是否允许我们自定义校验注解呢? -
常用算法 之五 数据校验(CRC 原理、LRC、奇偶校验、校验和)详解
2019-07-03 12:12:03尤其是在嵌入式软件开发中,经常要用到 CRC 算法对各种数据进行校验。生成的数字在传输或者储存之前计算出来并且附加到数据后面,然后接收方进行检验确定数据是否发生变化。 CRC 是数据流采用二进制除法(没有... -
记录Android jetpack MVVM使用DataBinding 表达式多条件判断&&检查验证EditText表单数据
2020-08-04 17:32:01在这种场景下,可以直接可以在布局文件中使用Databinding直接对Edittext的数据进行检查,若其中有一个信息为空,则登录按钮禁用,就可以省去在Activity写很多的检查代码。 1. 如何获取所有Edittext 输入的文本... -
spring boot中数据验证validated的使用
2018-07-23 16:43:49spring-boot中在Controller层里面可以用@validated来校验数据再进入业务逻辑层,如果数据异常则会统一抛出异常,方便异常中心统一处理。 比如,我们判断一个输入的用户名长度限制以及密码的正则验证. 使用流程 ... -
使用easyexcel做数据校验错误返回excel表格批注+背景
2020-03-27 22:16:11使用easyexcel做数据校验错误返回excel表格批注+背景 效果入这样 想要实现,每行错误单元格,写入错误批注还是很难得,因为easyexcel在2.2.0.bate1 版本才支持写入批注. 所有还没研究能不能实现. 现在按照模板只能实现... -
机器学习中的数据不平衡解决方案大全
2017-06-09 19:37:12在机器学习任务中,我们经常会遇到这种困扰:数据不平衡问题。 数据不平衡问题主要存在于有监督机器学习任务中。当遇到不平衡数据时,以总体分类准确率为学习目标的传统分类算法会过多地关注多数类,从而使得少数类...