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

目录

第 16 章:同步持久属性和计算属性
同步持久属性和计算属性
总结
在上一章,我们学到如何使用 JPA 回调在多用户环境的开发中定义默认属性。现在我们将看如何同步计算属性和持久属性。

同步持久属性和计算属性

正如我们所了解的,计算属性不允许筛选或排序列表,因此我们更喜欢使用 @Calculation 的持久属性。但是,@Calculation 属性仅对简单的算术计算有效。当你需要循环、条件、从数据库读取、连接外部服务器或一些复杂的逻辑时,@Calculation 是不够的。对于这些情况,您需要在 getter 中使用 Java 编写逻辑。但是,怎样才能做到这点但同时也保持列表中的排序和筛选呢?很简单,您可以使用两个属性,一个是计算的,一个是持久的,并使用 JPA 回调方法同步两者。您将在本节中学习如何做到这一点。
让我们在 Order 实体中添加一个名为 estimatedDeliveryDays 的新属性:
@Depends("date")
public int getEstimatedDeliveryDays() {
    if (getDate().getDayOfYear() < 15) {
        return 20 - getDate().getDayOfYear(); 
    }
    if (getDate().getDayOfWeek() == DayOfWeek.SUNDAY) return 2;
    if (getDate().getDayOfWeek() == DayOfWeek.SATURDAY) return 3;
    return 1;
}
这是一个纯计算属性,带有 Java 逻辑的 getter。它使用日期来计算预计交货的天数。这种情况不能只使用支持基本算术 @Calculation 来解决。
我们还必须将 estimatedDeliveryDays 添加到 Order 代码的默认 @View 声明中:
@View(extendsView="super.DEFAULT", 
    members=
        "estimatedDeliveryDays," + // 添加这一行
        "invoice { invoice }"
)
...
public class Order extends CommercialDocument {
结果是这样的:

business-logic_en050.png


因为 estimateDeliveryDays 中的 @Depends("date"),所以当用户界面中的日期更改时,该值都会重新计算。这样是对的,但是在列表模式时,您无法按预计交货天数排序或筛选。为了解决这个问题,我们添加了第二个属性,这一次是持久的。将下一个代码添加到您的 Order 实体:
@Column(columnDefinition="INTEGER DEFAULT 1")
int deliveryDays;
该注意的是,我们使用了 @Column(columnDefinition="INTEGER DEFAULT 1"),这会让 OpenXava 在创建新列时使用“INTEGER DEFAULT 1”,代表新列的默认值是1而不是null,也避免 int 属性出现错误。是的,在许多情况下,@Column(columnDefinition=) 是对表执行 UPDATE 的替代方法(正如我们在“第十四章:手动更改 schema”中所做的那样),不过它的问题就是依赖数据库。无论如何,这 columnDefinition 与我们的计算/持久属性同步的问题相切,@Column 并不是必需的,它只给 int 属性方便。
这个新 deliveryDays 属性将与 estimatedDeliveryDays 有相同的值,但 deliveryDays 跟它数据库表中的列一样是持久的。这里的棘手问题是让 deliveryDays 属性同步。为此,我们将在 Order 里使用 JPA 回调方法来实现这一点。每次创建新订单 (@PrePersist) 或更新 (@PreUpdate) 时,将estimatedDeliveryDays 的值同步给 deliveryDays 就足够了。
将新的 recalculateDeliveryDays() 方法添加到 Order 实体并使用 @PrePersist 和 @PreUpdate 注解,如下:
@PrePersist @PreUpdate 
private void recalculateDeliveryDays() {
    setDeliveryDays(getEstimatedDeliveryDays());
}
基本上,每次在数据库中新建 Order 以及更新时都会调用 recalculateDeliveryDays() 方法。
现在您可以试试 Order 模块,您将看到新建或修改订单时,数据库中的 deliveryDays 列会在保存后正确更新,它已准备好用于大量处理,也能筛选或排序列表。

总结

在本章中,您了解到在一个实体生命周期的不同时刻使用 JPA 回调方法来定义逻辑是多么容易,它允许我们在修改现有实体或新建实体时,同步持久和计算属性。

下载本课源代码

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