Hibernate.orgCommunity Documentation

第 2 章 Validation step by step

2.1. 定义约束
2.1.1. 字段级(field level) 约束
2.1.2. 属性级别约束
2.1.3. 类级别约束
2.1.4. 约束继承
2.1.5. 对象图
2.2. 校验约束
2.2.1. 获取一个Validator的实例
2.2.2. Validator中的方法
2.2.3. ConstraintViolation 中的方法
2.2.4. 验证失败提示信息解析
2.3. 校验组
2.3.1. 校验组序列
2.3.2. 对一个类重定义其默认校验组
2.4. 内置的约束条件
2.4.1. Bean Validation constraints
2.4.2. Additional constraints

在本章中,我们会详细的介绍如何使用Hibernate Validator 来对一个给定的实体模型进行验证. 还会介绍Bean Validation规范提供了哪些默认的约束条件和Hibernate Validator提供了哪些额外的. 让我们先从如何给一个实体添加约束开始.

Bean Validation 的约束是通过Java 注解(annotations)来标注的. 在本节中,我们会介绍如何使用这些注解(annotations)来标注一个实体模型. 并且,我们会区分三种不通的注解(annotations) 类型.

如果你的模型遵循JavaBeans规范的话, 你还可以把约束标注在属性上. 例 2.2 “属性级约束”例 2.1 “字段级(field level) 约束”的唯一不同就是它的约束是定义在属性级别上的.

注意

如果要定义约束在属性级别上的话,那么只能定义在访问器(getter)上面,不能定义在修改器(setter)上.


When using property level constraints property access strategy is used to access the value to be validated. This means the bean validation provider accesses the state via the property accessor method. One advantage of annotating properties instead of fields is that the constraints become part of the constrained type's API that way and users are aware of the existing constraints without having to examine the type's implementation.

提示

It is recommended to stick either to field or property annotations within one class. It is not recommended to annotate a field and the accompanying getter method as this would cause the field to be validated twice.

Bean Validation API不仅能够用来校验单个的实例对象,还能够用来校验完整的对象图.要使用这个功能,只需要在一个有关联关系的字段或者属性上标注@Valid. 这样,如果一个对象被校验,那么它的所有的标注了@Valid的关联对象都会被校验. 请看例 2.6 “Adding a driver to the car”.



如果校验Car的实例对象的话,因为它的driver属性标注了@Valid, 那么关联的Person也会被校验. 所以,如果对象Personname属性如果是null的话,那么校验会失败.

关联校验也适用于集合类型的字段, 也就是说,任何下列的类型:

  • 数组

  • 实现了java.lang.Iterable接口( 例如Collection, ListSet)

  • 实现了java.util.Map接口

如果标注了@Valid, 那么当主对象被校验的时候,这些集合对象中的元素都会被校验.


当校验一个Car的实例的时候,如果passengers list中包含的任何一个Person对象没有名字的话,都会导致校验失败(a ConstraintValidation will be created).

注意

对象图校验的时候是会被忽略null值的.

Validator 是Bean Validation中最主要的接口, 我们会在第 5.1 节 “Configuration 和 ValidatorFactory”中详细介绍如何获取一个Validator的实例, 现在先让我们来看看如何使用Validator接口中的各个方法.

Validator中有三个方法能够被用来校验整个实体对象或者实体对象中的属性.

这三个方法都会返回一个Set<ConstraintViolation>对象, 如果整个验证过程没有发现问题的话,那么这个set是空的, 否则, 每个违反约束的地方都会被包装成一个ConstraintViolation的实例然后添加到set当中.

所有的校验方法都接收零个或多个用来定义此次校验是基于哪个校验组的参数. 如果没有给出这个参数的话, 那么此次校验将会基于默认的校验组 (javax.validation.groups.Default). 第 2.3 节 “校验组”

第 3 章 创建自己的约束规则中,我们会看到,每个约束定义中都包含有一个用于提示验证结果的消息模版, 并且在声明一个约束条件的时候,你可以通过这个约束中的message属性来重写默认的消息模版, 可以参考例 2.13 “Driver”. 如果在校验的时候,这个约束条件没有通过,那么你配置的MessageInterpolator会被用来当成解析器来解析这个约束中定义的消息模版, 从而得到最终的验证失败提示信息. 这个解析器会尝试解析模版中的占位符( 大括号括起来的字符串 ). 其中, Hibernate Validator中默认的解析器 (MessageInterpolator) 会先在类路径下找名称为ValidationMessages.propertiesResourceBundle, 然后将占位符和这个文件中定义的resource进行匹配,如果匹配不成功的话,那么它会继续匹配Hibernate Validator自带的位于/org/hibernate/validator/ValidationMessages.propertiesResourceBundle, 依次类推,递归的匹配所有的占位符.

因为大括号{ 在这里是特殊字符,所以,你可以通过使用反斜线来对其进行转义, 例如:

  • \{ 被转义成 {

  • \} 被转义成 }

  • \\ 被转义成 \

如果默认的消息解析器不能够满足你的需求,那么你也可以在创建ValidatorFactory的时候, 将其替换为一个你自定义的MessageInterpolator, 具体请参考 第 5 章 Bootstrapping.

校验组能够让你在验证的时候选择应用哪些约束条件. 这样在某些情况下( 例如向导 ) 就可以对每一步进行校验的时候, 选取对应这步的那些约束条件进行验证了. 校验组是通过可变参数传递给validate, validatePropertyvalidateValue的. 让我们来看个例子, 这个实例扩展了上面的Car类,又为其添加了一个新的Driver. 首先, 类Person (例 2.12 “Person”) 的name属性上定义了一个@NotNull 的约束条件. 因为没有明确指定这个约束条件属于哪个组,所以它被归类到默认组 (javax.validation.groups.Default).

注意

如果某个约束条件属于多个组,那么各个组在校验时候的顺序是不可预知的. 如果一个约束条件没有被指明属于哪个组,那么它就会被归类到默认组(javax.validation.groups.Default).


接下来, 我们让类Driver (例 2.13 “Driver”) 继承自类Person. 然后添加两个属性,分别是agehasDrivingLicense. 对于一个司机来说, 要开车的话, 必须满足两个条件, 年满18周岁 (@Min(18)) 和你的有驾照(@AssertTrue). 这两个约束条件分别定义在那两个属性上, 并且把他们都归于DriverChecks组. 通过例 2.14 “Group interfaces”, 你可以看到, "DriverChecks组" 就是一个简单的标记接口. 使用接口( 而不是字符串) 可以做到类型安全,并且接口比字符串更加对重构友好, 另外, 接口还意味着一个组可以继承别的组.



最后, 我们给Car class (例 2.15 “Car”) 添加一个passedVehicleInspection的属性,来表示这个车是否通过了上路检查.


现在, 在我们的例子中有三个不同的校验组, Person.name, Car.manufacturer, Car.licensePlateCar.seatCount都属于默认(Default) 组, Driver.ageDriver.hasDrivingLicense 从属于 DriverChecks组, 而Car.passedVehicleInspectionCarChecks组中. 例 2.16 “Drive away”演示了如何让Validator.validate验证不同的组来得到不同的校验结果.

例 2.16. Drive away

public class GroupTest {


    private static Validator validator;
    @BeforeClass
    public static void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }
    @Test
    public void driveAway() {
        // create a car and check that everything is ok with it.
        Car car = new Car( "Morris", "DD-AB-123", 2 );
        Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );
        assertEquals( 0, constraintViolations.size() );
        // but has it passed the vehicle inspection?
        constraintViolations = validator.validate( car, CarChecks.class );
        assertEquals( 1, constraintViolations.size() );
        assertEquals("The car has to pass the vehicle inspection first", constraintViolations.iterator().next().getMessage());
        // let's go to the vehicle inspection
        car.setPassedVehicleInspection( true );
        assertEquals( 0, validator.validate( car ).size() );
        // now let's add a driver. He is 18, but has not passed the driving test yet
        Driver john = new Driver( "John Doe" );
        john.setAge( 18 );
        car.setDriver( john );
        constraintViolations = validator.validate( car, DriverChecks.class );
        assertEquals( 1, constraintViolations.size() );
        assertEquals( "You first have to pass the driving test", constraintViolations.iterator().next().getMessage() );
        // ok, John passes the test
        john.passedDrivingTest( true );
        assertEquals( 0, validator.validate( car, DriverChecks.class ).size() );
        // just checking that everything is in order now
        assertEquals( 0, validator.validate( car, Default.class, CarChecks.class, DriverChecks.class ).size() );
    }
}

首先我们创建一辆汽车然后在没有明确指定使用哪个校验组的情况下校验它, 可以看到即使passedVehicleInspection的默认值是false也不会校验出错误来. 因为定义在这个属性上的约束条件并不属于默认的校验组, 接下来,我们来校验CarChecks这个组, 这样就会发现car违反了约束条件, 必须让这个车先通过检测. 接下来,我们给这个车增加一个司机, 然后在基于DriverChecks来校验, 会发现因为这个司机因为还没有通过驾照考试, 所以又一次得到了校验错误, 如果我们设置passedDrivingTest属性为true之后, DriverChecks组的校验就通过了.

最后, 让我们再来校验所有的组中定义的约束条件,可以看到所有的约束条件都通过了验证.

By default, constraints are evaluated in no particular order and this regardless of which groups they belong to. In some situations, however, it is useful to control the order of the constraint evaluation. In our example from 第 2.3 节 “校验组” we could for example require that first all default car constraints are passing before we check the road worthiness of the car. Finally before we drive away we check the actual driver constraints. In order to implement such an order one would define a new interface and annotate it with @GroupSequence defining the order in which the groups have to be validated.

注意

如果这个校验组序列中有一个约束条件没有通过验证的话, 那么此约束条件后面的都不会再继续被校验了.


警告

一个校验组序列中包含的校验组和这个校验组序列不能造成直接或者间接的循环引用. 包括校验组继承. 如果造成了循环引用的话, 会导致GroupDefinitionException异常.

例 2.18 “校验组序列的用法”展示了校验组序列的用法.


The @GroupSequence annotation also fulfills a second purpose. It allows you to redefine what the Default group means for a given class. To redefine Default for a given class, add a @GroupSequence annotation to the class. The defined groups in the annotation express the sequence of groups that substitute Default for this class. 例 2.19 “RentalCar with @GroupSequence” introduces a new class RentalCar with a redefined default group. With this definition the check for all three groups can be rewritten as seen in 例 2.20 “testOrderedChecksWithRedefinedDefault”.



注意

因为不能在校验组和校验组序列中有循环依赖关系, 所以, 如果你想重定义一个类的默认组, 并且还想把Default组加入到这个重定义的序列当中的话, 则不能简单的加入Default, 而是需要把被重定义的类加入到其中.

The @javax.validation.GroupSequence annotation is a standardized Bean Validation annotation. As seen in the previous section it allows you to statically redefine the default group sequence for a class. Hibernate Validator also offers a custom, non standardized annotation - org.hibernate.validator.group.GroupSequenceProvider - which allows for dynamic redefinition of the default group sequence. Using the rental car scenario again, one could dynamically add the driver checks depending on whether the car is rented or not. 例 2.21 “RentalCar with @GroupSequenceProvider” and 例  “DefaultGroupSequenceProvider implementation” show how this use-case would be implemented.



Hibernate Validator comprises a basic set of commonly used constraints. These are foremost the constraints defined by the Bean Validation specification (see 表 2.2 “Bean Validation constraints”). Additionally, Hibernate Validator provides useful custom constraints (see 表 2.3 “Custom constraints provided by Hibernate Validator”).

表 2.2 “Bean Validation constraints” shows purpose and supported data types of all constraints specified in the Bean Validation API. All these constraints apply to the field/property level, there are no class-level constraints defined in the Bean Validation specification. If you are using the Hibernate object-relational mapper, some of the constraints are taken into account when creating the DDL for your model (see column "Hibernate metadata impact").

注意

Hibernate Validator allows some constraints to be applied to more data types than required by the Bean Validation specification (e.g. @Max can be applied to Strings). Relying on this feature can impact portability of your application between Bean Validation providers.

表 2.2. Bean Validation constraints

AnnotationSupported data types作用Hibernate metadata impact
@AssertFalseBoolean, booleanChecks that the annotated element is false.没有
@AssertTrueBoolean, booleanChecks that the annotated element is true.没有
@DecimalMaxBigDecimal, BigInteger, String, byte, short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number.被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.没有
@DecimalMinBigDecimal, BigInteger, String, byte, short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number.被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.没有
@Digits(integer=, fraction=)BigDecimal, BigInteger, String, byte, short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number.Checks whether the annoted value is a number having up to integer digits and fraction fractional digits.对应的数据库表字段会被设置精度(precision)和准度(scale).
@Futurejava.util.Date, java.util.Calendar; Additionally supported by HV, if the Joda Time date/time API is on the class path: any implementations of ReadablePartial and ReadableInstant.检查给定的日期是否比现在晚.没有
@MaxBigDecimal, BigInteger, byte, short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: String (the numeric value represented by a String is evaluated), any sub-type of Number.检查该值是否小于或等于约束条件中指定的最大值.会给对应的数据库表字段添加一个check的约束条件.
@MinBigDecimal, BigInteger, byte, short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: String (the numeric value represented by a String is evaluated), any sub-type of Number.检查该值是否大于或等于约束条件中规定的最小值.会给对应的数据库表字段添加一个check的约束条件.
@NotNullAny typeChecks that the annotated value is not null.对应的表字段不允许为null.
@NullAny typeChecks that the annotated value is null.没有
@Pastjava.util.Date, java.util.Calendar; Additionally supported by HV, if the Joda Time date/time API is on the class path: any implementations of ReadablePartial and ReadableInstant.检查标注对象中的值表示的日期比当前早.没有
@Pattern(regex=, flag=)String检查该字符串是否能够在match指定的情况下被regex定义的正则表达式匹配.没有
@Size(min=, max=)String, Collection, Map and arraysChecks if the annotated element's size is between min and max (inclusive).对应的数据库表字段的长度会被设置成约束中定义的最大值.
@ValidAny non-primitive type递归的对关联对象进行校验, 如果关联对象是个集合或者数组, 那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.没有

注意

On top of the parameters indicated in 表 2.2 “Bean Validation constraints” each constraint supports the parameters message, groups and payload. This is a requirement of the Bean Validation specification.

In addition to the constraints defined by the Bean Validation API Hibernate Validator provides several useful custom constraints which are listed in 表 2.3 “Custom constraints provided by Hibernate Validator”. With one exception also these constraints apply to the field/property level, only @ScriptAssert is a class-level constraint.

表 2.3. Custom constraints provided by Hibernate Validator

AnnotationSupported data types作用Hibernate metadata impact
@CreditCardNumberStringChecks that the annotated string passes the Luhn checksum test. Note, this validation aims to check for user mistakes, not credit card validity! See also Anatomy of Credit Card Numbers.没有
@EmailStringChecks whether the specified string is a valid email address.没有
@Length(min=, max=)StringValidates that the annotated string is between min and max included.对应的数据库表字段的长度会被设置成约束中定义的最大值.
@NotBlankStringChecks that the annotated string is not null and the trimmed length is greater than 0. The difference to @NotEmpty is that this constraint can only be applied on strings and that trailing whitespaces are ignored.没有
@NotEmptyString, Collection, Map and arraysChecks whether the annotated element is not null nor empty.没有
@Range(min=, max=)BigDecimal, BigInteger, String, byte, short, int, long and the respective wrappers of the primitive typesChecks whether the annotated value lies between (inclusive) the specified minimum and maximum.没有
@SafeHtml(whitelistType=, additionalTags=)CharSequenceChecks whether the annotated value contains potentially malicious fragments such as <script/>. In order to use this constraint, the jsoup library must be part of the class path. With the whitelistType attribute predefined whitelist types can be chosen. You can also specify additional html tags for the whitelist with the additionalTags attribute.没有
@ScriptAssert(lang=, script=, alias=)Any type要使用这个约束条件,必须先要保证Java Scripting API 即JSR 223 ("Scripting for the JavaTM Platform")的实现在类路径当中. 如果使用的时Java 6的话,则不是问题, 如果是老版本的话, 那么需要把 JSR 223的实现添加进类路径. 这个约束条件中的表达式可以使用任何兼容JSR 223的脚本来编写. (更多信息请参考javadoc)没有
@URL(protocol=, host=, port=, regexp=, flags=)StringChecks if the annotated string is a valid URL according to RFC2396. If any of the optional parameters protocol, host or port are specified, the corresponding URL fragments must match the specified values. The optional parameters regexp and flags allow to specify an additional regular expression (including regular expression flags) which the URL must match.没有

In some cases neither the Bean Validation constraints nor the custom constraints provided by Hibernate Validator will fulfill your requirements completely. In this case you can literally in a minute write your own constraints. We will discuss this in 第 3 章 创建自己的约束规则.