Java中的数据验证

原文链接:
翻译:CUBA China
CUBA- 官网 :
CUBA China 官网 :
【Java中的数据验证】

Java中的数据验证

文章插图

Java中的数据验证

文章插图
我经常看见很多项目没有数据验证的策略和意识 。他们的团队在交付日期的重压下,面对不清楚的需求,没有时间去考虑用合适并且统一的方法对数据进行验证 。所以在这样的项目中,到处能看见数据验证的代码:在前端JS中,在后端页面控制器中,在业务逻辑的bean中,在数据模型实体中,在数据库的约束和触发器中 。这些代码都是一些 if-else 的语句,抛出一些不同的未检查的异常,所以有时会很难找到这些该死的数据到底是在哪里做的验证 。因此 , 一段时间之后 , 当项目成长到足够大的时候就很难并且需要耗费很多精力来统一这些验证,并且后面的需求也一样模糊不清 。
那有没有做数据验证比较标准、优雅而且还简洁的方法呢?这个方法不会导致代码的不可读,这个方法能帮我们将大部分数据验证的代码维护在统一的地方,而且有没有可能一些流行框架的开发者已经替我们做了大部分的工作呢?
当然有!
作为我们CUBA平台的开发者来说,让我们的用户也遵循最佳实践非常重要 。我们认为,数据验证的代码应该是:
1. 可重用但不重复,遵循DRY原则(Don’t) 。
2. 用干净和自然的方式表达出来 。
3. 放在开发人员期望看到的地方 。
Java中的数据验证

文章插图
4. 能对不同数据来源的数据进行检查:用户输入,SOAP或者REST 调用等 。
5. 能处理并发 。
6. 由应用程序隐式统一调用而不需要手动调用这些检查代码 。
7. 能用简洁的弹窗为用户展示清晰,本地语言的消息 。
8. 遵循标准 。
这篇文章里 , 我将使用基于CUBA平台开发的应用程序来演示所有的例子 。由于CUBA是基于和的,所以这些例子对于使用JPA和bean验证的其他Java框架也适用 。
数据库约束验证
也许 , 最常用最直接的数据验证方法就是使用数据库级别的约束,比如非空,字符串长度,唯一索引等 。对于企业级应用来说,这个方法很自然,因为这种类型的软件通常都是以数据为中心 。但是,即便是这种情况,开发者也经常出错,在应用程序的各个数据层级分别定义了约束 。这个问题主要是由于开发人员的不同责任分工引起的 。
我们看一个几乎大家都会面对的例子,有的人甚至干过这样的事:) 。假设有个规定要求护照号码字段需要有10个数字 , 很可能到处都会做这个规则检查:数据库设计者用DDL检查,后台开发人员在相应的实体和REST服务中检查,最后前端工程师在客户端代码中检查 。之后这个需求变了 , 要求护照字段升到15个数字 。技术支持人员可能只修改了数据库约束,但是这样对于用户来说等于什么都没改,因为后台和前台的检查还没修改呢 。
大家都知道避免这个问题的方法,验证需要中心化 。在CUBA,这种验证的中心点在是实体的JPA注解 。基于这个元数据信息,CUBA 可以生成正确的DDL脚本并且能在客户端采用相应的验证器 。
Java中的数据验证

文章插图
此时,如果JPA注解改变的话,CUBA会自动更新DDL脚本以及生成数据库迁移脚本,所以下次部署项目的时候,新的基于JPA的限制将会在UI和DB生效 。
这种方式简单、也能实施到底层数据库级别,因此能完全防破解 。但是JPA注解的局限性在于,只能使用在最简单、可以用标准的DDL表述、而不需要引入特定数据库的触发器或者存储过程的情况 。所以基于JPA的约束可以用来保证实体字段是唯一的,或者必须的 , 抑或也能定义字段的最大长度 。还有,可以使用 @ 注解来为一组字段定义唯一性约束 。但也就这些了 。
Java中的数据验证

文章插图
如果在需要更加复杂的验证逻辑的的时候,比如检查某个字段的最大最小值或者对一个字段使用正则表达式进行验证,此时我们就需要使用众所周知的叫做“bean 验证” 的方法了 。
Bean验证
我们知道,遵循标准是很好的实践,通常这种方式有更长的生命周期而且有几千个项目实战证明过了 。Java 的 Bean验证是早就写在石头上的方案了:在JSR 380,349 和 303也有些成熟的实现: 和 BVal 。
很多开发者都熟悉这个方法,但是这个方法的好处却总是被低估 。用这个方法甚至可以很容易在遗留项目中添加数据验证 , 并且还能以清晰、直接、可靠最贴近业务逻辑的方式表达需要做的验证 。
使用Bean验证能为项目带来很多好处:
l验证逻辑集中在数据模型附近:使用最自然的方法定义针对值、方法和bean的约束,因此可以将OOP推进到下一个级别(验证也可以OOP) 。
lBean验证的标准提供了几十种开箱即用的验证注解比如@, @Size, @Min, @Max, @, @Email, @Past, 不太标准的比如 @URL, @,强大的 @,另外还有很多其他的 。
l不会受限于仅使用预定义的约束 , 还可以自定义约束注解 。可以定义一个注解来将其他几个注解绑定到一起,或者定义一个全新的注解,然后定义一个相应的Java类作为验证器 。比如,之前那个例子中,可以定义一个类级别的注解 @ 用来检查护照号码是否符合正确的格式,号码也许还依赖字段的值 。
l不止可以在类和字段上加约束,也可以添加到方法和方法参数上 。这个叫做“合同验证” , 后面会介绍 。
CUBA平台(以及一些其他平台)会在用户提交数据的时候自动调用这些验证,所以一旦验证失败用户会马上看到错误消息,不需要考虑手动执行这些bean验证 。
我们一起再看看护照号码验证的例子,但是这次我们还需要在实体添加几个其他的验证:
l人物姓名(例子中是用的英文名)至少有2个单词或者可以更多,必须是格式化很好的姓名 。检查的正则表达式很复杂,比如Ogier de Batz deComte d' 能通过检查,但是 R2D2 却不能通过 。
l人物身高的区间:0<