tags:

views:

3346

answers:

3

In my app I have these Hibernate-mapped types (general case):

class RoleRule {
  private Role role;
  private PermissionAwareEntity entity; // hibernate-mapped entity for which permission is granted
  private PermissionType permissionType; // enum

  @ManyToOne
  @JoinColumn(name = "ROLE_ID")
  public Role getRole() {
    return role;
  }
  public void setRole(Role role) {
    this.role = role;
  }

}

class Role {
  private Set<RoleRule> rules = new HashSet<RoleRule>(0);

  @OneToMany(cascade=CascadeType.ALL)
  @JoinColumn(name="ROLE_ID")
  public Set<RoleRule> getRules() {
    return rules;
  }
  public void setRules(Set<RoleRule> rules) {
    this.rules = rules;
  }

}

All classes have equals() & hashCode() overrides.

My application allows tweaking of roles (by sysadmins only, don't worry), and among other fields, allows creation of new role rules. When a new rule is created I try to create a new RoleRule object and insert it into the role's field rules. I call session.update(role) to apply the changes to the database.

Now comes the ugly part... Hibernate decides to do the following when closing the transaction and flushing:

  1. Insert the new rule into the database. Excellent.
  2. Update the other role fields (not collections). So far so good.
  3. Update the existing rules, even if nothing has changed in them. I can live with this.
  4. Update the existing rules again. Here's a paste from the log, including the automatic comment:
/* delete one-to-many row Role.rules */
update ROLE_RULE set ROLE_ID=null where ROLE_ID=? and ROLE_RULE_ID=?

Of course, all fields are not-null, and this operation fails spectacularly.

Can anyone try to explain why Hibernate would do this??? And even more important, how the frak do I get around this???

EDIT: I was so sure it was something to do with the mapping, and then my boss, on a whim, deleted the equals() and hashCode() in both classes, recreated them using Eclipse, and mysteriously this solved the problem.

I'm still very curious about my question though. Can anyone suggest why Hibernate would do this?

+1  A: 

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
)
toolkit
+2  A: 

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
+2  A: 

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)

HeDinges
I'm well aware of the importance of equals and hashcode, and how to overrride them. The great mystery here is that the old equals+hashcode are identical to the newly generated... and yet this solved my problem. Can you explain that?
Yuval