battmanz
battmanz

Reputation: 2296

JPA merge overwrites lazy loaded property

First let me say that I'm new to JPA. I've been tasked with maintaining some existing code that I did not write. I made one simple change, which was adding FetchType.LAZY to an existing @OneToOne annotation. This reduced the number of database calls being made when doing a query on one page in our application. However, it had the unintended consequence that on a separate page data is now being lost when doing a merge with the EntityManager. The merge works fine without FetchType.LAZY.

Here is some highly simplified code that illustrates the problem. The JPA entities:

@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
}

Note that I added FetchType.LAZY to the @OneToOne annotation on the child property (look to the far right). That did not exist before.

Next, there exists a view-scoped bean that queries the database for a specific Parent in the postConstruct method. Later on, in a method called via ajax, the parentis refreshed and the child's name is reassigned. Finally, at some point later on the parent is saved.

@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);
  }
}

The reason I post the code for the view-scoped bean is prior to asking this question on StackOverflow I wrote a single method that queries for a Parent, refreshes the parent, reassigns the child name, and then saves the Parent. However, I could not reproduce the problem if I did it all in a single method. So I'm guessing that somehow having these operations in separate methods that are called at separate times is part of the problem.

Anyways, the problem is that once I attempt to merge the Parent, JPA cascades the merge to the child (which is good), but the bad part is that it appears to "reload" the child from the database and overwrite my change. After the merge the change to the child's name is lost and not saved. How can I fix this while keeping FetchType.LAZY?

Upvotes: 1

Views: 350

Answers (1)

Chris
Chris

Reputation: 21145

You are cascading the refresh operation as well, wiping out any existing changes in the child object. This might be expected by the application, but now that the Parent->Child relationship is lazy, the timing of the refresh is also lazy and gets done when the relationship is first accessed. Any changes done before the selectedParent.getChild() access will be lost. Before you made it lazy, any changes to the child object before the refresh call would have been lost.

You will want to evaluate your cascade options and make sure they line up with what you really need from the application.

Upvotes: 1

Related Questions