In my app I have these Hibernate-mapped types (general case):
class RoleRule {
private Role role;
private PermissionAwareEntity entity; // hibernate-mapp
See the answer of the question 'Overriding equals and hashCode in Java'.
It explains how to override the equals and hashCode methods, which seemed to be your problem as it worked after having them rewritten.
Wrongly overriding them can lead hibernate to delete your collections and reinsert them. (as the hash key is used as keys in maps)
I'm not sure if this is the solution, but you might want to try:
@OneToMany(mappedBy = "role")
And not have the @JoinColumn annotation? I think both entities are trying to 'own' the association, which is why the SQL might be messed up?
Also, if you want to ensure only affected columns get updated, you can use a hibernate-specific annotation on the class:
@Entity
@org.hibernate.annotations.Entity(
dynamicInsert = true, dynamicUpdate = true
)
I've generally used two methods of updating a collection (the many side of a one-to-many) in Hibernate. The brute force way is to clear the collection, call save on the parent, and then call flush. Then add all of the collection members back and call save on the parent again. This will delete all and then insert all. The flush in the middle is key because it forces the delete to happen before the insert. It's probably best to only use this method on small collections since it does re-insert all of them.
The second way is harder to code, but more efficient. You loop through the new set of children and manually modify the ones that are still there, delete the ones that aren't, and then add the new ones. In pseudo-code that would be:
copy the list of existing records to a list_to_delete
for each record from the form
remove it from the list_to_delete
if the record exists (based on equals()? key?)
change each field that the user can enter
else if the record doesn't exist
add it to the collection
end for
for each list_to_delete
remove it
end for
save
I've searched the Hibernate forums for many hours trying to find the right way to solve this problem. You should be able to just update your collection to make it accurate and then save the parent, but as you've found out, Hibernate tries to detach the children from the parent before deleting them and if the foreign key is not-null, it will fail.
Brian Deterling's answer helped me to overcome the phantom delete. I wish he had put a real code. Here is what I got from his suggestion 1. Posting for someone to use it or to comment my code.
// snFile and task share many to many relationship
@PersistenceContext
private EntityManager em;
public SnFile merge(SnFile snFile) {
log.debug("Request to merge SnFile : {}", snFile);
Set<Task> tasks = taskService.findBySnFilesId(snFile.getId());
if(snFile.getTasks() != null) {
snFile.getTasks().clear();
}
em.merge(snFile);
em.flush();
if(tasks != null) {
if(snFile.getTasks() != null)
snFile.getTasks().addAll(tasks);
else
snFile.setTasks(tasks);
}
return em.merge(snFile);
}