openxava / 文档 / 第十二章:@Calculation 和集合总计

课程: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

目录

第十二章:@Calculation 和集合总计
@Calculation 的持久属性
集合的总计属性
总结
我们已经了解如何在集合中使用 @DefaultValueCalculator 注解来计算金额。现在我們将學如何使用 @Calculation 注解来实现总计。

@Calculation 的持久属性

有时计算的属性不是最佳选择。假设您在 Invoice 中有一个计算属性,為折扣(discount):
// 別添加到您的代码,这只是为了说明!
public BigDecimal getDiscount() {
    return getAmount().multiply(new BigDecimal("0.10"));
}
如果您需要处理所有折扣大于 1000 的发票,则必须编写类似以下代码:
// 別添加到您的代码,这只是为了说明!
Query query = getManager().createQuery("from Invoice"); // 查询中没设条件
for (Object o: query.getResultList()) { // 迭代所有对象
    Invoice i = (Invoice) o;
    if (i.getDiscount() // 查询每个对象
        .compareTo(new BigDecimal("1000")) > 0) {
            i.doSomething();
    }
}
您不能在查询中使用折扣作为条件来区分,因为折扣不在数据库中,它仅在 Java 的对象中,因此您必须实例化每个对象才能按折扣询问。在某些情况下,这种方式是一个不错的选择,但如果您的发票的数量非常大,并且只有少数发票的折扣大于 1000,那么这个流程将非常低效。所以我们有什么选择呢?
我们的替代方案是使用 @Calculation 注解。 @Calculation 是一个 OpenXava 注解,它允许将一个持久属性和一个计算相关联。您可以使用 @Calculation 定义折扣,如下:
// 别添加到您的代码,这只是为了说明!
@ReadOnly
@Calculation("amount * 0.10")
BigDecimal discount;
这是一个常规的持久属性,也就是说,在数据库中具有相应的列,但它有 @Calculation 定义的一个算法。这情况下是金额 * 0.10,因此每当用户在用户界面更改金额时,折扣将立即重新计算。当用户单击保存时,重新计算的值将保存在数据库中,就像在任何持久属性中一样。我们还使用 @ReadOnly 注解了折扣,因此它在外观上和行为类似于计算属性,不过您可以省略 @ReadOnly 以便用户可以修改计算值
@Calculation 属性最有用的地方在于它可以在条件中使用。您可以重写上面的代码,如下:
// 别添加到您的代码,这只是为了说明!
Query query = getManager().createQuery("from Invoice i where i.discount > :discount"); // 可以设条件
query.setParameter("discount", new BigDecimal(1000));
for (Object o: query.getResultList()) { // 仅迭代选定的对象
    Invoice i = (Invoice) o;
    i.doSomething();
}
通过这种方式,我们将选择记录的工作交给数据库的服务器,而不是 Java 的服务器。而且,折扣并不是每次都重新计算,而是已经计算并保存了。
这个在列表模式中也有影响,因为用户不能在计算属性上进行筛选或排序,但可以在带有 @Calculation 的持久属性上做到这一点:

business-logic_en025.png

@Calculation 在需要筛选和排序时是一个不错的选择,仅一个简单的计算就足够了。不过 @Calculation 其中一个缺点是,只有当用户更改计算器所使用的属性值时,才会重新计算它们的值。因此当您将新的 @Calculation 属性添加到已具有数据的实体时,您必须使用 SQL 更新表中新列的值。另一方面,如果您需要复杂的计算、循环或咨询其他实体,您仍然需要在 getter 中使用以您 Java 逻辑的计算属性。在最后这个情况,如果您需要在列表模式下对计算属性进行排序和筛选,则可以选择同时拥有计算属性和持久属性,并使用 JPA 回调方法同步它们的值(我们将在以后的课程看到回调方法)。

集合的总计属性

我们想在 Order 和 Invoice 中添加一些金额(总额)。增值税率、增值税、鏓合和合计缺一不可。为此,您只需在 CommercialDocument 类添加一些属性。下图显示了这些属性的用户界面:

business-logic_en030.png

將以下代碼添加到 CommercialDocument 中:
@Digits(integer=2, fraction=0) // 指定其长度
BigDecimal vatPercentage;
   
@ReadOnly
@Money
@Calculation("sum(details.amount) * vatPercentage / 100")
BigDecimal vat;

@ReadOnly
@Money
@Calculation("sum(details.amount) + vat")    
BigDecimal totalAmount;    
请注意我们是为何在 vat 跟 totalAmount 选择持久属性(@Calculation + @ReadOnly)而不是计算属性,这是因为这些算法很简单,并且对它们进行筛选和排序。另外,您可以在@Calculation 中看到如何使用 sum(details.amount) 来引用集合详细信息中金额列(总额列)的总和,这样我们就不需要 baseAmount 属性。另一方面,vatPercentage 是一个传统的持久属性,我们使用 @Digits(来自 Bean Validation 的注解,Java 基准验证)作为 @Column 的替代品来指定其大小。
现在您已经编写了 CommercialDocument 中的金额属性,就必须修改 detail 集合的属性列表, 来显示 CommercialDocument 的 the total properties(发票和订单)。让我们来看看:
abstract public class CommercialDocument extends Identifiable {
 
    @ElementCollection
    @ListProperties(
        "product.number, product.description, quantity, pricePerUnit, " +
        "amount+[" + 
        	"commercialDocument.vatPercentage," +
        	"commercialDocument.vat," +
        	"commercialDocument.totalAmount" +
        "]" 
    )
    private Collection<Detail> details;
 
    ...
}
总计等属性是实体中的常规属性(在本例中为商业文档),它们都放置在用户界面中集合列的下方。为此,在@ListProperties 中,会使用方括号来枚举它们,例如 amount[commercialDocument.totalAmount]。此外,如果您只想要一个列的总和,则不需要属性,而是在 @ListProperties 中,属性的后面加上一个+ 就足够了,例如 amount+。在我们的例子中,我们结合了两个,+ 和 [ ] 之间的总属性。
现在您可以在应用程序试试它。应该要与本章开头的图几乎一样。 “几乎”是因为 vatPercentage 还没有默认值。我们将在下一节中添加它。


总结

在本章中,您学到了如何使用 @Calculation 注解定义具有特定计算的持久属性,也看到了 @Digits 注解在定义字段类型和长度方面的使用方法。还使用带有 @ReadOnly +@Calculation 注解的持久属性来定义总属性,并且看到了如何使用 sum 来覆盖特定属性。

下载本课源代码

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