Categories

Hibernate is a very complete ORM library. One of the few ommisions is the possibility to map collections of collections. In this blog this omission is investigated and a solution is provided for the specific case of a Map of Sets.

Collection of Collections

Let’s first take a look at a simple collection mapping. Here a person has a list of friends.

Java

1

2

3

publicclassPerson{

privateList<Friend>friends;

}

XHTML

1

2

3

4

5

6

7

8

9

10

11

<class name="Person">

...

<list name="friends">

<key column="person_fk"/>

<index column="pos"/>

<one-to-many class="Friend"/>

</list>

</class>

<class name="Friend">

...

</class>

This will lead to the following tables:

Person

ID

1

Friend

ID

PERSON_FK

POS

1

1

0

2

1

1

The Friend table has a foreign key to the Person table (PERSON_FK) and it also contains a column with the index in the list (POS). This is a very common mapping (There are other ways to model this in the database schema, but that is not relevant for this discussion).

Now let’s say we want the Person-Friend relation to be a bit more specific. We have groups of friends, that have a certain common source, like relatives, schoolmates or any other type.

Java

1

2

3

publicclassPerson{

privateMap<FriendSource,List<Friend>>friends;

}

The collection has turned from a simple collection into a collection of collections. The relationship did not necessarily change from one-to-many to many-to-many. I choose to model the relation as one-to-many in the first situation (rather weird to think that a Friend is only your Friend!) so we can leave that like this as well.

There would be a very simple change in table layout that could accomodate this new situation:

Person

ID

1

Friend

ID

PERSON_FK

POS

SOURCE

1

1

0

CLASS_MATE

2

1

1

CLASS_MATE

3

1

0

RELATIVE

Only the Friend table has changed. The SOURCE column determines the key for the Map and the POS column determines the index in the list. The unique key on the Friend table has changed, though.

There is no way to configure Hibernate to handle this situation. The mapping would have to allow both a map-key and an index element, but it doesn’t. The only way for Hibernate to work with this is to introduce a new Entity; FriendGroup.

Java

1

2

3

4

5

6

publicclassPerson{

privateMap<FriendSource,FriendGroup>groups;

}

publicclassFriendGroup{

privateList<Friend>friends;

}

XHTML

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

<class name="Person">

...

<map name="groups">

<key column="person_fk"

<map-key type="FriendSource"column="source"/>

<one-to-many class="FriendGroup"/>

</map>

</class>

<class name="FriendGroup">

...

<list name="friends">

<key column="group_fk"/>

<index column="pos"/>

<one-to-many class="Friend"/>

</list>

</class>

<class name="Friend">

...

</class>

Person

ID

1

FriendGroup

ID

PERSON_FK

SOURCE

1

1

CLASS_MATE

2

1

RELATIVE

Friend

ID

GROUP_FK

POS

1

1

0

2

1

1

3

2

0

The new entity is mapped to a new table. The rationale behind this is that an Entity has identity (PK column in database and id field in code). A collection has no identity but belongs to an Entity. Collection elements have a foreign key to the primary key (identity) of the owner of the collection. For a collection of collections, the elements in the ultimate collection have no identity to point to, since the collection is not owned by an Entity, but by another collection. By introducing a new Entity, which is basically an identifier with a collection, we return to the simple situation.
The same holds for components. A component has no identity and therefore can not contain a collection, since there would be no identity to point to.

Introducing the concept of recursive collections (collections of collections) in Hibernate will be rather tricky. It touches upon a large part of the library. There is one case in which the problem can be resolved by simple means, this is in the case of a Map of Sets.

Map of Sets

Let’s say we have Persons and Orders and a Person has many orders and each order has a certain PaymentStatus.

Java

1

2

3

publicclassPerson{

privateMap<PaymentStatus,Set<Order>>ordersByPaymentStatus;

}

Using the above code would still be rather hard with Hibernate, so we’ll turn to the Apache commons-collections library for a convenient class

Java

1

2

3

publicclassPerson{

privateMultiMap ordersByPaymentStatus;

}

N.B.: We have to drop the generic parameters to the interface. The MultiMap extends the general Map interface but not the generic Map interface. The put method allows for simple values, but the get method returns a collection (list) of these values.

The MultiMap will maintain a list of items (values) for each key, but we will not worry about the order in those lists, and interact with them as simple java.util.Collections.

Because the implementation of the collection that we want to use (MultiHashMap) differs from the normal Map implementation that Hibernate uses (HashMap), we have to create a UserCollectionType. This is one of the interfaces that Hibernates provides for extension. This interface basically describes how Hibernate will interact with any collection implementation. The implementation of this UserCollectionType is pretty straightforward.

The first two methods are very simple and will probably be the same for every collection implementation. Note that the values() method of MultiHashMap will flatten the collection and return an iterator that will iterate over all values in the lists.
For a MultiMap to implement the indexOf(…) method we will have to inspect the list that is associated with the key to see if it contains the specified element.
The instantiate(…) and wrap(…) methods are related. The parameterless instantiate() method must return an implementation of the non-persistent collection type, in our case MultiHashMap. The second instantiate method must return a PersistentCollection. This is a specialized Hibernate collection, that knows how to persist collections of this Type. We will come back to this in greater detail later. The wrap(…) method will be used by Hibernate to transform a non-persistent collection into a persistent collection. The difference between the last two methods is that the wrapped collection is backed by the collection passed in and therefore already initialized, while the instantiated collection is used to load a new collection possibly lazily and therefore not yet initialized.
The replaceElements(…) method will copy the content from the original to the target, using the types for key and value to copy those objects. This implementation has to iterate both the map and the collections that are values in the map to copy each key and value in turn.

Persisting a MultiMap is very similar to persisting a normal Map. The only difference is that the values in the MultiMap are collections. Hibernate cannot handle collections as values, so we need to implement a specialized PersistentMultiMap. You can find the sources for both classes described here as attachements to this blog.

Java

1

2

3

4

5

6

7

8

9

10

publicclassPersistentMultiMapextendsPersistentMapimplementsMultiMap{

{

publicPersistentMultiMap(SessionImplementor session,MultiMap map){

super(session,map);

}

publicPersistentMultiMap(SessionImplementor session){

super(session);

}

The PersistentMultiMap extends PersistentMap because its behaviour is very similar to the Map. It also implements the MultiMap interface, so it can be substituted for MultiMap collections in entity objects. Because of this interface we have to implement one additional method:

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

publicObjectremove(Objectkey,Objectitem){

Objectold=isPutQueueEnabled()?readElementByIndex(key):UNKNOWN;

if(old==UNKNOWN){

write();

return((MultiMap)map).remove(key,item);

}else{

queueOperation(newRemoveItem(key,item));

returnold;

}

}

privateclassRemoveItemimplementsDelayedOperation{

privatefinalObjectkey;

privatefinalObjectitem;

privateRemoveItem(Objectkey,Objectitem){

this.key=key;

this.item=item;

}

publicObjectgetAddedInstance(){

returnnull;

}

publicObjectgetOrphan(){

returnitem;

}

publicvoidoperate(){

((MultiMap)map).remove(key,item);

}

}

The remove(Object key, Object item) method is the only addition of the MultiMap to the Map interface. It removes the item from the collection that is keyed by the key. The implementation is mostly copied from any of the collection operations in the PersistentCollections. The DelayedOperation mechnism is used by Hibernate to support operations on not fully initialized collections.

One of the most important changes to the behaviour in the PersistentMap is the entries(…) method. This method is used by Hibernate to iterate all elements (both key and value). In the case of a MultiMap we have to provide an iterator that will return all the values in the collections in the MultiMap, together with the key that the collection is bound to as a Map.Entry. To this end we introduce a new implementation of the Iterator interface.

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

@Override

publicIterator entries(CollectionPersister persister){

returnnewKeyValueCollectionIterator(super.entries(persister));

}

privatefinalstaticclassKeyValueCollectionIteratorimplementsIterator{

privatefinalIterator parent;

privateObjectkey;

privateIterator current;

privateKeyValueCollectionIterator(Iterator parent){

this.parent=parent;

move();

}

publicbooleanhasNext(){

returnkey!=null;

}

publicObjectnext(){

if(key==null){

thrownewNoSuchElementException();

}else{

DefaultMapEntry result=newDefaultMapEntry(key,current.next());

if(!current.hasNext()){

move();

}

returnresult;

}

}

privatevoidmove(){

while(this.parent.hasNext()){

Map.Entry entry=(Entry)this.parent.next();

key=entry.getKey();

current=((Collection)entry.getValue()).iterator();

if(current.hasNext()){

return;

}

}

key=null;

}

publicvoidremove(){

thrownewUnsupportedOperationException();

}

}

The rest of the class is concerned with dirty checking. To do dirty checking and lazy updates, Hibernate keeps a snapshot of all entities and collections that are loaded from the database into memory. At flush time the cuirrent state of the objects in memory is compared with the snapshot state and differences are persisted to the database. The snapshot is created by the getSnapshot(CollectionPersister persister) method. While taking a snapshot, deep copies have to be made of all values in the collection. The snapshot is another MultiMap.

The snapshot is held in the session and can be fetched through the getSnapshot() method. The equalsSnapshot(…), getDeletes(…), needsInserting(…) and needsUpdating(…) methods are used to check the differences.
The equalsSnapshot(…) is used as a shortcut to determine whether further dirty checking is necessary.
The getDeletes(…) method must return an iterator over either the keys or the values that are removed from the collection. These values are not necessarily removed from the database, but the relation between the two entities (collection owner and value) will be removed. Note that in this implementation items that have moved in the map will also be returned. A move will be regarded by a remove and insert by this implementation (this is also the case in the standard PersistentMap implementation from Hibernate).
The needsInserting(…) and needsUpdating(…) check the snapshot for the collection that is associated with the same key as the entry that is to be checked. The implementation relies on a correctly implemented equals(…) and hashCode() method on the entity type (as does a lot of Hibernate code).

Now this code can simply be used by adding a collection-type attribute to your Map in the Hibernate mapping.

XHTML

1

2

3

4

5

6

7

8

9

10

11

12

<class name="Person">

...

<map name="ordersByPaymentStatus"

collection-type="com.xebia.hibernate.MultiMapType">

<key column="person_fk"/>

<map-key column="status"/>

<one-to-many class="Order"/>

</map>

</class>

<class name="Order"table="ORRDER">

...

</class>

Now the map will be kept in sync with the database state.

This code will also work for many-to-many associations or value maps. Unfortunately in these situations the HBM2DDL functionality in Hibernate will generate a unique constraint that prevents the Map from containing multiple values for the same key. If you do not use HBM2DDL but write your schema manually the code will work for you. The reason why Hibernate does not generate this unique constraint for one-to-many maps is that the foreign-key column for a one-to-many is normally in the childs entity column. Since children without a parent might be present in the database the unique constraint might pose problems in the case of multiple nulls.

Conclusion

Hibernate can be used to persist MultiMaps. This requires some custom code and a good understanding of how to extend Hibernate. Extending Hibernate to work with other types of recursive collections might be a lot more difficult.

Unfortunately, the attachements to this blog have been lost. The complete source code of the (2) classes needed for the mechanism described in this blog are included in the text of the blog.
To use the code, simply cut and paste the classes together.

I tried the Map of Sets solution.
See below for the mapping. In spite of cascade option set the AclObject is not geting saved and
i get the following errors
“object references an unsaved transient instance – save the transient instance before flushing”

Thanks for this, it’s been very useful. However, MultiMap.putAll doesn’t work with the above code (it uses the existing PersistentMap.putAll that puts the value collections into the MultiMap rather than each key, value pair). The following works for me if added to PersistentMultiMap: