JPA 合并覆盖延迟加载 属性

JPA merge overwrites lazy loaded property

首先让我说我是 JPA 的新手。我的任务是维护一些我没有编写的现有代码。我做了一个简单的更改,即将 FetchType.LAZY 添加到现有的 @OneToOne 注释中。这减少了在我们的应用程序中的一个页面上进行查询时进行的数据库调用次数。然而,它有一个意想不到的后果,即在一个单独的页面上,当用 EntityManager 执行 merge 时,数据现在丢失了。合并在没有 FetchType.LAZY.

的情况下工作正常

这里有一些高度简化的代码可以说明问题。 JPA 实体:

@Entity
@Table(name = "parent")
public class Parent {

  @OneToOne(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
  private Child child;

  @Column(name = "name")
  private String name;

  // getters/setters omitted for brevity
}

@Entity
@Table(name = "child")
public class Child {

  @OneToOne
  @JoinColumn(name = "parent_id", nullable = false)
  private Parent parent;

  @Column(name = "name")
  private String name;

  // getters/setters omitted for brevity
}

请注意,我在 child 属性 的 @OneToOne 注释中添加了 FetchType.LAZY(看最右边)。这在以前是不存在的。

接下来,存在一个 view-scoped bean,它在 postConstruct 方法中查询数据库中的特定 Parent。稍后,在通过 ajax 调用的方法中,刷新 parent 并重新分配 childname。最后,在稍后的某个时候 parent 被保存。

@ManagedBean
@ViewScoped
public class DemoBean {

  @Inject
  private ParentDao parentDao;

  private Parent selectedParent;

  @PostConstruct
  public void postConstruct() {
    selectedParent = parentDao.findByName("Bob");
  }

  // Called via ajax sometime after postConstruct.
  public void changeChildName() {
    // Under the hood, `parentDao.refresh` calls `entityManager.refresh`.
    selectedParent = parentDao.refresh(selectedParent);
    selectedParent.getChild().setName("Joe");
  }

  // Called via ajax sometime after changeChildName.
  public void save() {
    // Under the hood, `parentDao.merge` calls `entityManager.merge`.
    selectedParent = parentDao.merge(selectedParent);
  }
}

之所以我 post view-scoped bean 的代码是在 Whosebug 上问这个问题之前,我写了一个查询 Parent 的方法,刷新 parent,重新分配 child name,然后保存 Parent。但是,如果我用一种方法完成所有操作,我将无法重现该问题。所以我猜测以某种方式在不同时间调用的不同方法中进行这些操作是问题的一部分。

无论如何,问题是一旦我尝试 merge Parent,JPA 将 merge 级联到 child(这很好),但是不好的部分是它似乎 "reload" 来自数据库的 child 并覆盖了我的更改。合并后,对 child 名称的更改将丢失且未保存。如何在保持 FetchType.LAZY 的同时解决此问题?

您也在级联刷新操作,清除子对象中的所有现有更改。这可能是应用程序所期望的,但是现在 Parent->Child 关系是惰性的,刷新的时机也很惰性,并在第一次访问该关系时完成。 selectedParent.getChild() 访问之前所做的任何更改都将丢失。在使其变得惰性之前,在刷新调用之前对子对象所做的任何更改都将丢失。

您需要评估您的级联选项并确保它们符合您对应用程序的真正需求。