Hibernate验证器

第 4 章 Hibernate验证器

 http://hibernate.org/validator/documentation/getting-started/#applying-constraints

注解是一种为领域模型(domain model)指定不变约束的简洁而幽雅的方法。例如,你能 表示一个属性永远不为null,一个帐户余额一定是正值,等等。这些域模型约束通过为bean中的属性添加 注解来加以声明。随后一个验证器(validator)会读取并检查这些约束。验证机制可以执行于应用程序中的 不同层(表现层、数据访问层),而不必复述任何(前述)这些规则。Hibernate验证器正为这一目的而设计的。

Hibernate验证器工作在两个层次上。第一层,它能检查内存中一个类的实例是否违反约束。 第二层,它能将约束应用于Hibernate元模型上,并将它们融入生成的数据库schema中。

每个约束注解(constraint annotation)和一个验证器实现关联,该验证器负责检查位于实体实例上的约束。 一个验证器也能(可选地)将约束应用于Hibernate元模型上,让Hibernate生成表示这一约束的DDL。使用合适的事件监听器,你能 让Hibernate在插入和更新时执行检查操作。Hibernate验证器并不局限于同Hibernate一起使用。 你能在你应用程序的任何地方方便地使用它。

在运行时检查实例时,Hibernate验证器返回违反约束的信息, 这些信息以一个InvalidValue数组的形式返回。 除了众多其他信息外,InvalidValue包含了一个错误描述消 息,该信息可以内嵌与注解相捆绑的参数值(例如长度限制),以及能被提取至ResourceBundle的消息字串。

4.1. 约束

 

4.1.1. 什么是约束?

 

约束通过注解表示。一个约束通常有一些用来参数化约束限制的属性。约束应用于带注解的元素。

4.1.2. 内建约束

 

Hibernate验证器有些内建约束,这些约束覆盖了大多数的基本数据检查。随后我们会看到, 你不必受制于这些内置约束,因为一分钟内就可以写出你自己的约束。

表 4.1. 内建约束

注解应用目标运行时检查Hibernate元数据影响
@Length(min=, max=) 属性(String) 检查字符串长度是否符合范围 列长度会被设到最大值
@Max(value=) 属性 (以numeric或者string类型来表示一个数字) 检查值是否小于或等于最大值 对列增加一个检查约束
@Min(value=) 属性(以numeric或者string类型来表示一个数字) 检查值是否大于或等于最小值 对列增加一个检查约束
@NotNull 属性 检查值是否非空(not null) 列不为空
@Past 属性(date或calendar) 检查日期是否是过去时 对列增加一个检查约束
@Future 属性 (date 或 calendar) 检查日期是否是将来时
@Pattern(regex="regexp", flag=) 属性 (string) 检查属性是否与给定匹配标志的正则表达式相匹配(见 java.util.regex.Pattern )
@Range(min=, max=) 属性(以numeric或者string类型来表示一个数字) 检查值是否在最小和最大值之间(包括临界值) 对列增加一个检查约束
@Size(min=, max=) 属性 (array, collection, map) 检查元素大小是否在最小和最大值之间(包括临界值)
@AssertFalse 属性 检查方法的演算结果是否为false(对以代码方式而不是注解表示的约束很有用)
@AssertTrue 属性 检查方法的演算结果是否为true(对以代码方式而不是注解表示的约束很有用)
@Valid 属性 (object) 对关联对象递归的进行验证。如果对象是集合或数组,就递归地验证其元素。如果对象是Map,则递归验证其值元素。
@Email 属性(String) 检查字符串是否符合有效的email地址规范。

4.1.3. 错误信息

 

Hibernate验证器提供了一组默认的错误提示信息,它们被翻译成多种语言(如果你的语言不在其中,请给 我们寄一个补丁)。你可以在org.hibernate.validator.resources.DefaultValidatorMessages.properties 之外创建ValidatorMessages.propertiesValidatorMessages_loc.properties 文件并改变相应的键值,籍此覆盖那些(默认)信息。你甚至可以在写自己的验证器 注解时添加你自己的附加消息集。

或者你可以以编程方式检查bean的验证规则并提供相应的ResourceBundle

4.1.4. 编写你自己的约束

 

扩展内建约束集是极其方便的。任何约束都包括两部分:约束描述符(注解) 和约束验证器(实现类)。下面是一个简单的用户定义描述符:

@ValidatorClass(CapitalizedValidator.class)
@Target(METHOD) 
@Retention(RUNTIME)
@Documented
public @interface Capitalized {
    CapitalizeType type() default Capitalize.FIRST;
    String message() default "has incorrect capitalization";
}

type参数描述属性应该如何被大写。这是一个完全依赖于注解业务(逻辑)的用户 参数。

message是用于描述约束违规的默认字符串,它是强制要求的。你可以采取硬编码的方式, 或者通过Java ResourceBundle机制将message的部分/全部内容提取至外部文件。一旦发现message中{parameter}字符串, 就会在{parameter}这个位置注入相应的参数值(在我们的例子里Capitalization is not {type}会生成 Capitalization is not FIRST), 可以将message对应的整个字符串提取至外部文件ValidatorMessages.properties,这也是一种良好实践。 见Error messages

@ValidatorClass(CapitalizedValidator.class)
@Target(METHOD) 
@Retention(RUNTIME)
@Documented
public @interface Capitalized {
    CapitalizeType type() default Capitalize.FIRST;
    String message() default "{validator.capitalized}";
}

...
#in ValidatorMessages.properties
validator.capitalized=Capitalization is not {type}

如你所见{}符号是递归的。

为了将一个描述符连接到它的验证器实现,我们使用@ValidatorClass 元注解。验证器类参数必须指定一个实现了Validator<ConstraintAnnotation> 的类。

我们现在要实现验证器(也就是实现规则检查)。一个验证器实现能检查一个属性的值 (实现PropertyConstraint),并且/或者可以修改hibernate映射元数据 (实现PersistentClassConstraint),籍此表示数据库级的约束。

public class CapitalizedValidator 
        implements Validator<Capitalized>, PropertyConstraint {
    private CapitalizeType type;

    //part of the Validator<Annotation> contract, 
    //allows to get and use the annotation values
    public void initialize(Capitalized parameters) {
        type = parameters.type();
    }

    //part of the property constraint contract
    public boolean isValid(Object value) {
        if (value==null) return true;
        if ( !(value instanceof String) ) return false;
        String string = (String) value;
        if (type == CapitalizeType.ALL) {
            return string.equals( string.toUpperCase() );
        }
        else {
            String first = string.substring(0,1);
            return first.equals( first.toUpperCase();
        }
    }
}

如果违反约束,isValid()方法将返回false。更多例子请参考内建验证器实现。

至此我们只看到属性级的验证,你还可以写一个Bean级别的验证注解。Bean自身会被传递给验证器, 而不是bean的属性实例。只要对bean自身进行注解即可激活验证检查。在单元测试套件中还可以找到一个小例子。

4.1.5. 注解你的领域模型

 

既然你现在已经熟悉注解了,那么对语法也应该很清楚了。

public class Address {
    private String line1;
    private String line2;
    private String zip;
    private String state;
    private String country;
    private long id;
    
    // a not null string of 20 characters maximum
    @Length(max=20) 
    @NotNull
    public String getCountry() {
        return country;
    }
    
    // a non null string
    @NotNull
    public String getLine1() {
        return line1;
    }

    //no constraint    
    public String getLine2() {
        return line2;
    }
    
    // a not null string of 3 characters maximum
    @Length(max=3) @NotNull
    public String getState() {
        return state;
    }

    // a not null numeric string of 5 characters maximum
    // if the string is longer, the message will 
    //be searched in the resource bundle at key 'long'
    @Length(max=5, message="{long}")
    @Pattern(regex="[0-9]+")
    @NotNull
    public String getZip() {
        return zip;
    }
    
    // should always be true
    @AssertTrue
    public boolean isValid() {
        return true;
    }

    // a numeric between 1 and 2000
    @Id @Min(1)
    @Range(max=2000)
    public long getId() {
        return id;
    }
}

上面的例子只展示了公共属性验证,你还可以对任何可见度的字段(field)进行注解。

@MyBeanConstraint(max=45)
public class Dog {
    @AssertTrue private boolean isMale;
    @NotNull protected String getName() { ... };
    ...
}

你可以对接口进行注解。Hibernate验证器会检查给定bean所扩展或实现的所有父类和接口, 籍以读取相应的验证器注解(信息)。

public interface Named {
    @NotNull String getName();
    ...
}

public class Dog implements Named {

    @AssertTrue private boolean isMale;

    public String getName() { ... };

}

在验证Dog bean时会检查name属性的有效性(不为null)。

4.2. 使用验证器框架

 

Hibernate验证器旨在实现多层数据验证,我们在一处表示约束(带注解的域模型),然后将其运用于 应用程序的不同层。

4.2.1. 数据库schema层次验证

 

无须额外手续,Hibernate Annotations会自动将你为实体定义的约束翻译为映射元数据。例如,如果你的实体 的一个属性注解为@NotNull,在Hibernate生成的DDL schema中这列会被定义为 not null

4.2.2. Hibernate基于事件的验证

 

Hibernate验证器有两个内建Hibernate事件监听器。当一个PreInsertEvent 或PreUpdateEvent发生时,监听器会验证该实体实例的所有约束,如有违反会抛出一个异常。 基本上,在Hibernate执行任何插入和更新前对象会被检查。这是激活验证过程的最便捷最简单的方法。当遇到约束 违规时,事件会引发一个运行时InvalidStateException,该异常包含一个描述每个错误的 InvalidValue数组。

<hibernate-configuration>
    ...
    <event type="pre-update">
        <listener 
          class="org.hibernate.validator.event.ValidatePreUpdateEventListener"/>
    </event>
    <event type="pre-insert">
        <listener 
          class="org.hibernate.validator.event.ValidatePreInsertEventListener"/>
    </event>
</hibernate-configuration>

注意

在使用Hibernate Entity Manager时,Validation框架会被自动激活。如果bean不带验证注解, 就不会有性能损失。

4.2.3. 程序级验证

 

Hibernate验证器能应用于你应用程序代码中的任何地方。

ClassValidator personValidator = new ClassValidator( Person.class );
ClassValidator addressValidator = new ClassValidator( Address.class, ResourceBundle.getBundle("messages", Locale.ENGLISH) );

InvalidValue[] validationMessages = addressValidator.getInvalidValues(address);

头两行为执行类检查而准备Hibernate验证器。第一行依赖于嵌入在Hibernate验证器内的错误 消息(见Error messages),第二行为这些消息准备资源包。这些代码只执行一次, 并将验证器进行缓存处理,这种方式是一种良好实践。

第三行真正验证了Address实例并返回一个InvalidValue数组。 你的应用程序逻辑随后可以对错误做出响应。

除了针对整个bean你还可以

https://docs.jboss.org/hibernate/annotations/3.4/reference/zh_cn/html/validator.html

 

posted @ 2017-01-16 21:43  沧海一滴  阅读(1321)  评论(0编辑  收藏  举报