【问题标题】:The correct way to update the fields of an @Entity that depend on other entities' state更新依赖于其他实体状态的@Entity 字段的正确方法
【发布时间】:2023-03-25 14:56:01
【问题描述】:

我们遇到了一个问题,我们必须维护一个日期字段 - 并且最好保留它的值 - 其中日期字段取决于其他实体的状态。用 JPA2 + Hibernate + Spring 解决这个问题的正确方法是什么?

基本上,我们有三个实体,我们称它们为ParentChildConfdependentDate 由依赖于其他实体状态的复杂业务规则计算得出。具体来说,应该是 Parent#date 减去 Conf#delay 跳过所有周末或节假日(持久数据)。

问题是每当Parent#date 发生变化时,所有孩子的dependentDates 都应该重新计算。每当 Child 对象的 conf 更改时,也会发生这种情况。目前有一种基于Child对象关系计算新日期的服务方法(我们称之为calculateDate())。

我们面临着应该在哪里计算字段值的问题。我想出了三个可能的解决方案。其中哪一个(或者可能是其他)是更新依赖于其他实体状态的实体字段的首选方式?

  1. JPA EntityListener
    • JPA2 规范不鼓励这样做,因为 EntityListener 不应访问其他实体
    • 字段总是正确的
  2. 更新的 Spring AOP 方面
    • 逻辑隐藏在其他类中,不容易看到
    • 切入点无法捕获所有持久/更新事件的风险
  3. 每个开发者都为自己
    • 总是,在更新 Parent 或 Child 类的实例时,开发人员必须记住调用服务的 calculateDate() 方法
    • 这应该在 dao 层还是服务层?
    • 开发者忘记添加调用,导致错误状态的风险

下面的例子展示了省略注解配置的实体关系。

实体

public class Parent {
     LocalDate date;
     Set<Child> children;     // one-to-many
}

public class Conf {
     int delay;
}

public class Child {
     LocalDate dependentDate;
     Conf conf;               // many-to-one
     Parent parent;           // many-to-one
}

【问题讨论】:

  • 这不应该是一个答案,而是一个“选项 4”:您可以在数据库中添加一个触发器,在更新其他表时更新日期字段。这当然只有在以下情况下才有效:a)您的 dbms 支持触发器,b)您愿意将业务逻辑移出您的主软件,并且 c)您的实体是“无状态的”(就像在 spring mvc 设置中一样)。
  • @realsim 您所说的“无状态”实体是什么意思?在我看来,实体只是一个状态(免责声明,我知道 JPA 但不知道 Spring)
  • 我的意思是在休眠会话提交后,它不会被再次使用(因为触发器的实体不在会话认为的状态)。如果会话将被重用,它将以 StaleObjectStateException 结束。但是,如果会话因请求/响应完成而关闭,并且将在下一个请求时创建另一个会话,则该方法会很好。 - 我不喜欢自己将业务逻辑从代码中移出,但有时触发器是必要的......

标签: java spring hibernate jpa persistence


【解决方案1】:

依赖于多个实体状态的复杂业务规则属于服务层。应用程序中不应该有太多地方更改父母的日期和孩子的 conf,因此您不应该有太多地方必须进行新的日期计算。

如果你真的有这么多地方可以这样做,你可以清楚地表明必须通过将 ChildDateUpdater 实例(ChildDateUpdater 是一个接口)传递给父日期的设置器和子日期的设置器来重新计算日期conf。这表明每次更改这两个字段时都必须做一些事情:

在父母中:

public void setDate(Date date, ChildDateUpdater childDateUpdater) {
    this.date = date;
    for (Child child : children) {
        childDateUpdater.updateChildDate(child);
    }
}

在儿童中:

public void setConf(Conf conf, ChildDateUpdater childDateUpdater) {
    this.conf = conf;
    childDateUpdater.updateChildDate(this);
}

【讨论】:

  • 我们最终在model 包中创建了接口,并在服务层中创建了相应的实现。依赖字段的设置器是公开的,但 ConfParent 的日期设置器由更新程序接口提供。
【解决方案2】:

将您的数据访问封装在 DAO 中。当要保存 Parent 时,检查其值是否有任何更改,如果有,则相应地更新子级。

realsim 的触发选项也是可行的(基本上这是在不同级别实现的相同想法),但是您将失去可移植性(并且还会出现已加载的子项实例未更新的问题) .

【讨论】:

  • 使用 JPA/Hibernate,DAO 通常用于从数据库中检索数据。这些数据以实体的形式返回,可以更改并且其状态会自动持久化到数据库中。典型的 JPA 应用程序不会调用任何 DAO.save() 方法来修改数据库。
  • @JBNizet 好的,我明白了,我更习惯 EJB 的 JPA 风格。
  • EJB 的风格和我描述的一样。 JPA 实体附加到持久性上下文,链接到事务,当事务提交时,所有附加的实体状态都会自动刷新到数据库。
  • @JBNizet 与 JSF 和 EJB 一起工作,JPA 会话在请求之间未保持打开的事实“迫使”我在 JSF 控制器中组织我的主要逻辑,而 EJB 仅作为 DAO(和最简单的逻辑)。也许我错了,你能指出一个很好的网络设计例子吗?
猜你喜欢
  • 1970-01-01
  • 2021-07-17
  • 2021-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-25
  • 2021-09-14
相关资源
最近更新 更多