openxava / 文档 / 第十九章:验证替代方案

课程:1. 入门教学 | 2. 基本域模型(上) | 3. 基本域模型(下) | 4. 优化用户界面 | 5. 敏捷开发 | 6. 映射式超类继承 | 7. 实体继承 | 8. 视图继承(View) | 9. Java 属性 | 10. 计算属性 | 11. 用在集合的 @DefaultValueCalculator | 12. @Calculation 和集合总计 | 13. 从外部文件的 @DefaultValueCalculator | 14. 手动更改 schema | 15. 多用户时默认值的计算 | 16. 同步持久属性和计算属性 | 17. 从数据库中的逻辑 | 18. 使用 @EntityValidator 进行验证 | 19. 验证替代方案 | 20. 删除时验证 | 21. 自定义 Bean Validation 注解 | 22. 在验证中调用 REST 服务 | 23. 注解中的属性 | 24. 改进标准行为 | 25. 行为与业务逻辑 | 26. 参照与集合 | A. Architecture & philosophy | B. Java Persistence API | C. Annotations | D. Automated testing

目录

第十九章:验证替代方案
使用 JPA 回调方法进行验证
在 setter 中验证
使用 Bean Validation 进行验证
总结
在上一章,您了解到如何使用 @EntityValidator 进行验证。在本章,您将学到如何更轻松的执行相同的验证。

使用 JPA 回调方法进行验证

我们将尝试用另一种更简单的方法来执行上一章的验证,只须将验证逻辑从验证器类转移到 Order 实体,在本例中是 @PrePersist 和 @PreUpdate 方法。
首先,删除 DeliveredToBeInvoiceValidator 类。然后从 Order 实体中删除 @EntityValidator 注解:
//@EntityValidator( // 删除 @EntityValidator 注解
//    value=com.yourcompany.invoicing.validators.DeliveredToBeInInvoiceValidator.class,
//    properties= {
//        @PropertyValue(name="year"),
//        @PropertyValue(name="number"),
//        @PropertyValue(name="invoice"),
//        @PropertyValue(name="delivered")
//    }
//)
public class Order extends CommercialDocument {
我们将在 Order 类中再次添加验证,将 validate() 方法添加到您的 Order 类:
@PrePersist @PreUpdate // 在新建或更改之前
private void validate()  throws Exception {
    if (invoice != null && !isDelivered()) { // 验证逻辑
        // Bean Validation 的验证异常
        throw new javax.validation.ValidationException(
            XavaResources.getString(
                "order_must_be_delivered",
                getYear(),
                getNumber()
            )
        );
    }
}
此验证会在保存订单之前执行,如果失败则抛出 ValidationException。这个异常来自 Bean Validation 框架,这样 OpenXava 就会知道它是一个验证异常。现在只用一种方法就完成验证了。
一个实体只允许一个 @PrePersist 和一个 @PreUpdate 方法,因此在运行以上代码前,必须注释 recalculateDeliveryDays() 中的 @PrePersiste 和 @PreUpdate 注解,方法如下:
// @PrePersist @PreUpdate // 注释这些注解
private void recalculateDeliveryDays() {
    setDeliveryDays(getEstimatedDeliveryDays());
}
别担心,我们稍后会取消注释。尽管 JPA 只允许一个 @PrePersist 和 @PreUpdate 方法,但还是可以只创建一个回调方法并从中调用其他所需的方法,不过在此案例不需要,因为我们不会保留这验证的方法。
现在,试着在发票添加未送达的订单,可以看到如上一章所显示一样的验证错误。

在 setter 中验证

另一种进行验证的方法是将验证逻辑放在 setter 方法中,这是一个简单的方法。
首先,我们先取消 recalculateDeliveryDays() 中 @PrePersist 和 @PreUpdate 的注释,同时从 Order 实体中删除 validate() 方法:
@PrePersist @PreUpdate // 将注解添加回来
private void recalculateDeliveryDays() {
    setDeliveryDays(getEstimatedDeliveryDays());
}
    
// 刪除 validate() 方法

// @PrePersist @PreUpdate // 在新建或更改之前
// private void validate()  throws Exception {
//     if (invoice != null && !isDelivered()) { // 验证逻辑
//         // Bean Validation 的验证异常
//         throw new javax.validation.ValidationException(
//             XavaResources.getString( 
//                 "order_must_be_delivered",
//                 getYear(),
//                 getNumber()
//             )
//         );
//     }
// }    
再来将以下 setter:setInvoice() 方法添加到 Order:
public void setInvoice(Invoice invoice) {
    if (invoice != null && !isDelivered()) { // 验证逻辑
        // Bean Validation 的验证异常
        throw new javax.validation.ValidationException(
            XavaResources.getString(
                "order_must_be_delivered",
                getYear(),
                getNumber()
            )
        );
    }
    this.invoice = invoice; // 常规的setter
}
这与上两个方法的运作方式完全相同。这一个很像 @PrePersist/@PreUpdate 的方案,只是它不依赖 JPA,它是一个基本 Java 的实现。

使用 Bean Validation 进行验证

作为最后一个选项,也是最短的一个:是将验证逻辑放在一个使用数据校验 @AssertTrue 注解的布尔方法(boolean)。
要实现这个方案,首先删除 setInvoice() 方法:
// 移除 setter 方法

// public void setInvoice(Invoice invoice) {
//    if (invoice != null && !isDelivered()) { // 验证逻辑
//        // Bean Validation 的验证异常
//        throw new javax.validation.ValidationException(
//            XavaResources.getString(
//                "order_must_be_delivered",
//                getYear(),
//                getNumber()
//            )
//        );
//    }
//    this.invoice = invoice; // 常规的setter
// }
然后将 isDeliveredToBeInvoice() 方法添加到您的 Order 实体:
@AssertTrue( // 在保存之前會確認此方法是否返回 true,不是的話則抛出异常
    message="order_must_be_delivered" // 如果为 false 的错误消息
)
private boolean isDeliveredToBeInInvoice() {
    return invoice == null || isDelivered(); // 验证逻辑
}

在之前的验证方法中,错误的消息是使用两个参数构造的(year 和 number),它们在 i18n 文件中,分别代表 {0}/{1}。而对于带有@AssertTrue 的验证方法,我们不能使用这两个参数来构造我们的错误消息,但我们可以在消息的定义中声明 已验证 bean 的属性和合格属性(properties 和 qualified properties),因此须在 invoicing-messages_zh.properties 中,把:
order_must_be_delivered=订单 {0}/{1} 必须已送达才能添加到发票中
更改为:
order_must_be_delivered=订单 {year}/{number} 必须已送达才能添加到发票中
可以看到我们将 {0}/{1} 更改为 {year}/{number}。 OpenXava 将使用那些正要更新但不满足验证条件的 Order 的 year 和 number 值填充 {year}/{number}。
这是最简单的验证方法,因为只需对带有验证的方法进行注解。数据校验会负责在保存时调用该方法,如果验证不成功则抛出相应的 ValidationException。

总结

在本章,您学到了几种在 OpenXava 应用程序中进行验证的方法。在下一课中,您将学习如何在移除时进行验证,由此您将已探索所有类型的验证。

下载本课源代码

对这节课有什么问题吗? 前往论譠 一切都顺利吗? 前往第二十章