1

在我的 OpenXava 应用程序中,我有一个带有 @OneToMany 实体集合的实体来创建主从结构。主实体 Invoice 有一个总的持久属性,每次用户添加、删除或更改细节时我都想更新它。OpenXava 生成的用户界面是这样的:

@OneToMany 详细信息集合

总计位于金额列的页脚,并在添加、修改或删除一行时更新。我使用 JPA 回调方法实现了这个效果,特别是细节类中的@PostPersist、@PostUpdate 和@PostRemove。

这是我的发票实体的代码:

@Entity @Getter @Setter
@View(members=
    "year, number, date;" +
    "customer;" +
    "details;" 
)
@Tab(properties="year, number, date, customer.name, total")
public class Invoice extends Identifiable {
    
    @DefaultValueCalculator(CurrentYearCalculator.class)
    @Column(length=4) @Required
    int year;
    
    @Column(length=6) @Required
    int number;
    
    @Required @DefaultValueCalculator(CurrentDateCalculator.class) 
    Date date;
    
    @ManyToOne(optional=false, fetch=FetchType.LAZY)
    Customer customer;
    
    @Stereotype("MONEY")
    BigDecimal total;
    
    @OneToMany(mappedBy="invoice", cascade = CascadeType.ALL)
    @ListProperties("product.number, product.description, unitPrice, quantity, amount[invoice.total]")
    List<InvoiceDetail> details;
            
    public void recalculateTotal() {
        BigDecimal sum = BigDecimal.ZERO;
        for (InvoiceDetail detail: details) {
            sum = sum.add(detail.getAmount());
        }
        total = sum;
    }
            
}

这对于 InvoiceDetail 实体:

@Entity @Getter @Setter
public class InvoiceDetail extends Identifiable {
    
    @ManyToOne
    Invoice invoice;
        
    @ManyToOne(optional=false, fetch=FetchType.LAZY)
    Product product;

    @Required 
    BigDecimal unitPrice;
        
    @Required
    int quantity;

    @Depends("unitPrice, quantity") 
    public BigDecimal getAmount() {
        return new BigDecimal(getQuantity()).multiply(getUnitPrice()); 
    }
    
    @PostPersist @PostUpdate @PostRemove
    private void recalculateInvoiceTotal() {
        invoice.recalculateTotal();
    }
    
}

上面的代码运行良好。但是,为了使用@ElementCollection 而不是@OneToMany 集合,我对其进行了重构,因此OpenXava 将生成一个用户可以在其中内联编辑详细信息的UI,方式如下:

@ElementCollection 详细信息集合

为此,我将 Invoice 实体中的集合定义更改为:

@ElementCollection
@ListProperties("product.number, product.description, unitPrice, quantity, amount[invoice.total]")
List<InvoiceDetail> details;

我将 InvoiceDetail 重构为 @Embeddable:

@Embeddable @Getter @Setter
public class InvoiceDetail {
    
    @ManyToOne(optional=false, fetch=FetchType.LAZY)
    Product product;

    @Required 
    BigDecimal unitPrice;
        
    @Required
    int quantity;

    @Depends("unitPrice, quantity") 
    public BigDecimal getAmount() {
        return new BigDecimal(getQuantity()).multiply(getUnitPrice()); 
    }
    
    @PostPersist @PostUpdate @PostRemove
    private void recalculateInvoiceTotal() {
        // invoice.recalculateTotal(); I no longer can access to invoice
        System.out.println("[InvoiceDetail.recalculateInvoiceTotal()] "); // NEVER PRINTED
    }
    
}

第一个问题是我无法从 InvoiceDetail 访问发票,但更糟糕的是 recalculateInvoiceTotal() 方法没有执行,从来没有。也就是说,JPA 回调方法不会在 @ElementCollection 的 @Embeddable 中执行。

JPA回调方法可以在@Embeddable中执行吗?有没有办法解决这个案子?

4

1 回答 1

0

JPA 回调方法用于优化实体生命周期。@Embeddable 对象没有自己的生命周期,但它们的周期与其容器实体相关联。

JPA 2.2 规范在第 3.5.2 节中声明:

“实体生命周期回调方法可以在实体侦听器类和/或直接在实体类或映射的超类上定义。”

请注意,它没有说明任何关于可嵌入的内容。

解决方案是将回调方法逻辑从@Embeddable 移到容器@Entity 中。对于您的情况,例如从 InvoiceDetail 嵌入中删除 recalculateInvoiceTotal() :

@Embeddable @Getter @Setter
public class InvoiceDetail {
    
    // ...      

    // REMOVE THIS METHOD 
    // @PostPersist @PostUpdate @PostRemove
    // private void recalculateInvoiceTotal() {
    //  invoice.recalculateTotal();
    // }
        
}

然后用@PrePersist @PreUpdate 标记Invoice 实体中已经存在的方法recalculateTotal(),因此:

@PrePersist @PreUpdate 
public void recalculateTotal() {
    BigDecimal sum = BigDecimal.ZERO;
    for (InvoiceDetail detail: details) {
        sum = sum.add(detail.getAmount());
    }
    total = sum;
}

并且总属性将像您之前的代码一样与 @OneToMany 集合同步。

于 2021-04-30T16:33:10.060 回答