« Home | My Desk » | Using Agile Practices » | Automated Tests are Great » | Changing Mindset » | Business Knowledge and Software Projects » | Reading Styles » | Java and Access Specifiers » | Why refactoring helps? » | No New Year Resolutions for Me » | Learning Tips »

Hibernate Mappings & Encapsulated Collections

Collections must be encapsulated. Also I am using Hibernate as the persistence solution. I have a uni-directional many to many relationship between Parent and Child. My java code looks like this:

Parent {
String name;
Set children = new HashSet();

public void getChildren() {
return Collections.unmodifiableCollection(children);
}

private void setChildren(Set children) {
this.children = children;
}

public void addChild(Child obj) {
///.....
}

public void removeChild(Child obj) {
///....
}
}

Child {
String name;
//......
}

Now I have hibernate mapping like this:

For parent:

set name="children" table="ParentChildren"
key column="parent_child_id"
many-to-many column="child_id" class="Child"


Hibernate code:

session.createCriteria(Parent.class).list()

When I execute above code something funny happens:All join table records are deleted and re-inserted.

The problem after digging through HIbernate documentation was this:

You must return exactly the same collection that Hibernate sets else it will try to synchronize it with database. As such it deleted and inserted as I was returnign a read only collection, which was different from what I got from hibernate.

Now if I pass a collection thats same I end up violating encapsulation.

To my set I added inverse="true" attribute

set name="children" inverse="true"

But this is an incorrect solution that works!. If you look up the definiton of inverse it means:

  1. Inverse signifies a bi-direction association
  2. Inverse = true says that this end is not responsible for association maintenance.

As such inverse = true stopped the inserts/deletes. But then mine was a uni-direcitional relationship and this was wrong. Because as of now I am only reading the records its fine. But for manipulating children, inverse=true wont work. Becasue whatever I do with child collection wont be persisted to the db.

The challenge was thus to achieve both: encapsulation of collection and proper usage of hibernate.

The solution looks like this:

Parent {
private Set getChildren() {
return this.children; //same as what hibernate sets..make it private
}
public Iterator children() {
return this.children.iterator();
//now clients cant modify the collection without invokding add/remove methods
}
}


Also the mapping remains the same as originally..viz without the inverese attribute because this is unidirectional association. Also semantically this is correct because now children manipulation will be persisted to the database.

Solution summary:

  1. Make your get set methods on a collection private
  2. Add a method that returns an iterator to your collection
  3. Have add/remove encapsulation mehtods on your collection
  4. Look up hibernate docs to specify your mappings based upon if its unidirectional/bidirecitonal association.
  5. Remeber that cascade and inverse are orthogonal concepts.

The following links will make it all clear: