JPA 回调方法
将业务逻辑添加到模型的另一种有用方法是使用 JPA 回调方法。一个回调方法会在实体生命周期的某个特定时刻中作为持久对象调用。也就是说,您可以指定一些实体的保存、读取、删除或修改的逻辑。
在本章,我们将看到 JPA 回调方法的一些实际应用。
多用户时默认值的计算
到目前为止,我们一直在使用 @DefaultValueCalculator 计算发票和订单的序号。该序号会在用户点击新建发票或订单时计算默认值。因此,当多个用户同时点击“新建”按钮时,他们都会得到相同的数字。在多用户环境下不该是这样,生成序号最好的方法是在保存时生成它。
我们将使用 JPA 回调方法来实现序号的生成。 JPA 允许您将一个类的任何方法标记在其生命周期的任何时候执行。我们指定它在保存 CommercialDocument 之前计算。顺便改进计算方法,让订单和发票有不同编号计算。
在 CommercialDocument 实体中添加 calculateNumber() 方法:
@PrePersist // 在第一次保存对象之前执行
private void calculateNumber() throws Exception {
Query query = XPersistence.getManager()
.createQuery("select max(i.number) from " +
getClass().getSimpleName() + // 這樣它对 Invoice 和 Order 都有效
" i where i.year = :year");
query.setParameter("year", year);
Integer lastNumber = (Integer) query.getSingleResult();
this.number = lastNumber == null ? 1 : lastNumber + 1;
}
此代码与 NextNumberForYearCalculator 的代码相同,差别是使用 getClass().getSimpleName() 而不是“CommercialDocument”。 getSimpleName() 方法返回不带包的类的名称,也就是实体名称。订单为“invoice”,发票为“Order”。因此,我们可以为 Order 和 Invoice 获得不同的编号。
JPA 规范声明您不应在回调方法中使用 JPA API。因此,从严格的 JPA 角度来看,上述方法是不合法的。但是,Hibernate(OpenXava 默认使用的 JPA 实现)允许您在 @PrePersist 中使用它。而由于 JPA 是进行此计算最简单的方法,因此我们使用它。
现在您可以从项目中删除 NextNumberForYearCalculator 类,并修改 CommercialDocument 的 number 属性以避免使用它:
@Column(length=6)
// @DefaultValueCalculator(value=NextNumberForYearCalculator.class, // 删除这几行
// properties=@PropertyValue(name="year")
// )
@ReadOnly // 用户无法修改
int number;
要注意的是,除了删除 @DefaultValueCalculator 之外,我们还添加了 @ReadOnly 注解。这意味着用户不能输入或修改号码。这才是正确的方法,因为数字是在保存对象时生成的,因此用户键入的值始终会被覆盖。