Java 计算属性wrt JPA。这对我来说行吗?

Java 计算属性wrt JPA。这对我来说行吗?,java,jpa,enums,Java,Jpa,Enums,我有一个场景,其中有两个标签需要配置。标签的名称为“过期”和“过期”。我在数据库中只有一个名为“日期”的字段。它是“Out”还是“In”在运行时由枚举“Scenario”的值决定。但是,我需要实际向用户显示Out-Date和In-Date,以便用户可以选择1个或两个。我听说计算领域概念it JPA将在这方面提供帮助。这是真的吗?还是有其他方法可以实现这一点。下面是一些示例代码 日期 情景 @Override @Convert("EnumConverter") @Column(name = "SC

我有一个场景,其中有两个标签需要配置。标签的名称为“过期”和“过期”。我在数据库中只有一个名为“日期”的字段。它是“Out”还是“In”在运行时由枚举“Scenario”的值决定。但是,我需要实际向用户显示Out-Date和In-Date,以便用户可以选择1个或两个。我听说计算领域概念it JPA将在这方面提供帮助。这是真的吗?还是有其他方法可以实现这一点。下面是一些示例代码

日期

情景

@Override
@Convert("EnumConverter")
@Column(name = "SCENARIO")
public Scenario getScenario() {
    return scenario;
}

场景是任何值为OUT(1)的枚举,IN(2)

在JPA中并没有计算属性

您可以使用
@Transient
注释创建未持久化但基于其他字段计算的属性:

@Transient
public DateTime getInDate() {
    if (scenario == Scenario.IN) {
        return date;
    }
    return null;
}

@Transient
public DateTime getOutDate() {
    if (scenario == Scenario.OUT) {
        return date;
    }
    return null;
}
或者,如果您使用的是Hibernate,则可以使用专有注释
@Formula

@Formula("case when SCENARIO = 2 then DATE else NULL end")
@Convert("DateTimeConverter")
private DateTime inDate;

@Formula("case when SCENARIO = 1 then DATE else NULL end")
@Convert("DateTimeConverter")
private DateTime outDate;
我更喜欢第一种选择,因为:

  • 使用单元测试更容易进行测试
  • 在单元测试中使用实体更容易
  • 它不需要专有的扩展
  • 一般来说,SQL的可移植性可能存在一些问题,尽管在这种情况下,当SQL 92兼容时,它不适用于这里

我能解决的唯一问题是,在最简单的方法中,我们通过向客户机公开实体的内部(
scenario
date
属性)来放弃封装。但是您可以始终使用accessor
protected来隐藏这些属性,JPA仍将处理这些属性。

要计算JPA实体中的属性,您可以使用JPA回调

请参阅此Hibernate文档。(注意:JPA回调并不特定于hibernate,它是最新JPA2.1的一部分)。 还有这个OpenJpa

以下实体生命周期类别有一个Pre和Post事件,实体管理器可以拦截该事件以调用方法:

  • 持续->
  • 删除->
  • 更新->
  • 加载->(此…,无前置)
假设您想从名为MyEntity的实体中的两个持久化实体字段label1和label2计算复杂标签:

正如@Dawid所写的,您必须用@Transient注释complexLabel,以便让持久性忽略它们。若不这样做,持久性将失败,因为MyEntity对应表中并没有这样的列

使用@PostLoad注释,实体管理器在从持久性加载MyEntity的任何实例之后,立即调用computeComplexLabel()方法。 因此,@PostLoad注释方法最适合放置加载后实体属性增强代码

下面是JPA 2.1规范中关于后加载的摘录:

实体的PostLoad方法是在实体被加载后调用的 从数据库或数据库加载到当前持久性上下文中 对其应用刷新操作后。后装 方法在返回或访问查询结果之前调用 在遍历关联之前

编辑


正如@Dawid所指出的,如果您想在实体更新后立即计算这个临时字段,您也可以使用@PostUpdate,并在需要时使用其他回调。

没有计算属性,但JPA回调有助于在实体加载后计算属性。这是另一个选项。然而,这种方法的问题是,当您更改实体时,以这种方式计算的属性不会更新。您还需要使用
@postapdate
注释。坦率地说,在容易计算的领域中使用JPA生命周期方法会引入不必要的复杂性,并使单元测试更加困难。此外,你需要非常小心。对于处于分离状态的实体,生命周期方法(尤其是
@postapdate
)将不起作用,会给您留下陈旧的数据。我同意@Dawid对postapdate的看法(因为您经常希望在实体更新后再次计算此计算属性)。但是,使用JPA回调优化此计算,因为它仅在加载(@PostLoad)或更新(@postpdate)后执行,而不是每次获得值时执行,如示例中所示调用getter。然后,我使用它使getter非常简单,这样它们就不会计算任何东西,只返回字段值。关于测试,我经常用集成或组件测试来说明这一点,使用一个真正的持久化单元和一个真正的DB或内存中的单元。我同意你的观点,@Rémi,在计算需要大量资源的情况下。然后我们可以考虑使用JPA生命周期方法。然而,在大多数情况下,特别是在问题中描述的情况下,IMHO的性能提高并不能证明引入的复杂性是合理的(如果我正确的话,只有1个if语句)。一些团队成员可能不知道或从未使用过JPA生命周期方法。编写简单的单元测试而不是复杂设置的集成测试的可能性也很诱人。集成测试的设置并不太复杂,它们很好地检查您是否遗漏了实体定义和查询中的某些内容。(使用漂亮的DSL插入您的设备,效果更佳)。(你不需要阿奎利安来做这件事)但我同意你的例子,这真的不是必需的。这取决于计算的复杂程度。。。对于这个字符串连接(complexLabel)的示例,我发现它非常复杂,不能作为getter的一部分。(看起来我们很快就要加入聊天室了。)你好,你终于找到了解决问题的正确方法了吗?
@Formula("case when SCENARIO = 2 then DATE else NULL end")
@Convert("DateTimeConverter")
private DateTime inDate;

@Formula("case when SCENARIO = 1 then DATE else NULL end")
@Convert("DateTimeConverter")
private DateTime outDate;
@Entity
public class MyEntity {

    private String label1;

    private String label2;

    @Transient
    private String complexLabel;

    @PostLoad
    @PostUpdate // See EDIT
    // ...
    public void computeComplexLabel(){
        complexLabel = label1 + "::" + label2;
    }
}