Wednesday, October 13, 2010

Protect Hibernate Collection

Suppose you have a unidirectional one-to-many association between department and employee.The department meta configuration uses eager loading for its employees; and the cascade is "all, delete-orphan".

A developer usually makes the following set association in the department pojo:

public class Department {
  private Set employees;

  public SetgetEmployees() {
      if (this.employees == null) {
        this.employees = new HashSet();
      }
      return this.employees;
  }

  public void setEmployees(Set employees) {
    this.employees = employees;
  }

} //end of class Department

The problem is with the method setEmployees(). Suppose you loaded a department object along with its 10 employees to your front-end UI, then you removed 2 employees - employee1 and employee2, form the set and added a new one employee11.
If you pass your new set of employees in a new Java Set instance and call setEmployees(), you will not get what you want.
This is the result: employee1 and employee2 are not deleted from your back-end DB as you expect. But the new employee11 was indeed inserted into the back-end DB.

This is why. When Hibernate initializes the employees set, it replaces it with its own version of set "PersistentSet" for bookkeeping among other reasons.
So if you remove the 2 employees from Hibernate's set, it will remember your deleting action. Otherwise Hibernate simply doesn't know your want to delete anything.

This will further cause unique constraint problem if you later changed your mind and don't want to delete a previously removed employee from the set.
For example, you first removed employee1 from the set, then you rolled it back by creating a new employee instance that has the same identify value(suppose the identify property is SSN and your DB has a unique constraint on SSN).
You are allowed to put the new employee into the set because you have removed the original employee1 from the set. But when you try to save the set into the database, you will get a unique constraint problem because the original employee1 is not deleted from the DB.

So you should defensively change method setEmployees() to protected and add some helper method. So the Department class looks like:

public class Department {
  private Set employees;

  public SetgetEmployees() {
      if (this.employees == null) {
        this.employees = new HashSet();
      }
      return this.employees;
  }

  //leaves it to Hibernate
  protected void setEmployees(Set employees) {
    this.employees = employees;
  }
  public boolean addEmployee(Employee employee) {
    return getEmployees().add(employee);
  }
  public boolean removeEmployee(Employee employee) {
    return getEmployees().remove(employee);
  }

} //end of class Department



Lastly why could you still save the new employ11 even using a new Java Set instance. This is because the new employee11's ID value is either null or 0 that is different from the id's "unsaved-value".

1 comment: