I have Principal and PrincipalRoles entity classes. They have a bi-directional “parent-child” relationship defined like this:
Principal has:
@OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, orphanRemoval = true, mappedBy = "principal")
Collection<PrincipalRoles> rolesByPrincipalCollection;
PrincipalRoles has:
@JoinColumn(name = "principal", referencedColumnName = "id", nullable = false, insertable = true, updatable = true, unique = false)
@ManyToOne(fetch = FetchType.LAZY)
Principal principal;
I also have a master-detail web page in the Web module of a Java EE application. The page uses the entity classes and their corresponding facades contained in the EJB module of the application.
The page obtains the Principal object and later updates it using methods of a Stateless EJB, PrincipalFacadeBean, which basically execute the find and merge methods of the Entity Manager. The Persistence Context is Container-Managed.
The page can add and remove several PrincipalRoles and then merge the Principal successfully. It can also add and remove several PrincipalRoles, update JUST ONE of the previously added and then merge the Principal; but it always fails when attempting to update more than one in a single merge operation. In those cases, no exception is raised but nothing is updated in the database. Not even other fields of Principal, like name, e-mail, etc.
The application runs on GlassFish 5.1.0, Java 1.8.0_281, EclipseLink 2.7.4, PostgreSQL 13.
I’ve been dealing with this problem for a few days now and I'm running out of ideas. Any assistance you can provide would be greatly appreciated.
Addendum to answer @crizzis questions:
I’m not sure what the service method would be. The page bean calls the façade directly using this method:
@Override
public List<Principal> merge(List<Principal> list) {
return principalFacade.merge(list);
}
In master-detail pages the list contains only the master. This is the façade’s merge method:
@Override
public List<Principal> merge(List<Principal> list) {
List<Principal> m = new ArrayList<>();
for (Principal o : list) {
m.add(_manager.merge(o));
}
return m;
}
These are the equals
and hashCode
methods of PrincipalRoles. Actually, these methods have the same template in all entity classes.
@Override
public boolean equals(Object obj) {
if (obj instanceof PrincipalRoles) {
PrincipalRoles that = (PrincipalRoles) obj;
return that == this || ObjUtils.equals(id, that.id);
}
return false;
}
@Override
public int hashCode() {
return Objects.hashCode(id);
}
This is ObjUtils.equals
:
public static boolean equals(Object a, Object b) {
return (a == b) || (a == null && b == null) || (a != null && b != null && a.equals(b));
}
The principal field of the updated PrincipalRoles is the Principal object being merged (at least their System.identityHashCode
matches). I also checked the value of an unmodified PrincipalRoles. That was null
.
All I had to do was change the
@ManyToOne
of the fieldprincipal
in PrincipalRoles from@ManyToOne(fetch = FetchType.LAZY)
to@ManyToOne(fetch = FetchType.EAGER)
. I still don't understand why, but it works! Thanks again to @crizzis and @Chris. Their questions led me to the answer.