Reputation: 2296
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 parent
is 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
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