Member Menu
 
 Monthly JBoss newsletter:
 
Java Persistence with Hibernate
CaveatEmptor

Bidirectional one-to-many with an indexed collection

Hibernate 2 does not support bidirectional (inverse="true") one-to-many associations with an indexed collection (list, map or array) as the "many" end.

If you want to keep the inverse="true" attribute and want to use an indexed collection, you have to handle the index of the collection manually.

The following solution works with a list.

The same method can be used when using an array or a map. (See below)


Mapping

<class name="net.sf.test.Parent" table="parent">
  <id name="id" column="id" type="long" unsaved-value="null">
    <generator class="sequence">
      <param name="sequence">SEQ_DEFAULT</param>
    </generator>
  </id>
  <list name="children" lazy="true" inverse="true">
      <key column="parent_id"/>
      <index column="index_col"/>
      <one-to-many class="net.sf.test.Child"/>
  </list>
</class>

<class name="net.sf.test.Child" table="child">
  <id name="id" column="id" type="long" unsaved-value="null">
    <generator class="sequence">
      <param name="sequence">SEQ_DEFAULT</param>
    </generator>
  </id>
  <many-to-one name="parent" column="parent_id" not-null="true"/>
  <property name="index" column="index_col" type="int" update="true" insert="true"/>
</class>

The inverse="true" is set to the one side.

The column name for the index property on the child is the same as the index column of the one-to-many mapping on the parent.


Code

The implementation of the Child getIndex and setIndex methods is as follows:

public class Child
{
  ...
  public int getIndex()
  {
    return this.getParent().getChildren().indexOf(this);
  }

  private void setIndex(int index)
  {
    // not used, calculated value, see getIndex() method
  }
  ...
}

By having the index value calculated, Hibernate will mark all Childs that have their index changed dirty. (If you switch 2 items from place, when you insert a new child, ...)


Note: For arrays and maps, there will be a different implementation of the getIndex method.

Map with String as keys:

  public String getIndex()
  {
    if (this.getParent().getChildMap().containsValue(this)
    {
      // return the key value for this child
    }
    return null;
  }

Hibernate 3

Hibernate 3 allows you to use indexed collections in a bidirectional association without workaround, just use this mapping:

Parent 
<list inverse="false"/> 
</list> 

Child 
<many-to-one insert="false" update="false"/> 

To summarize, you need to let the collection be the owner side.


  NEW COMMENT

Does it really work ? 08 Feb 2005, 04:54 grucha
I used this solution, and received exception:
"net.sf.hibernate.LazyInitializationException: cannot access loading 
collection"
inside getIndex(). I looked into Hibernate sources. During 
initialization of collection, every element is initialized. After 
initialization of single element it is inserted into second level 
cache. ChacheEntry class then invokes persister.getPropertyValues
(object). And here is the problem. getIndex() is invoked (through 
getPropertyValues) befor collection is initialized. There is solution 
to this problem, but very, very ugly.

    public int getIndex()
    {
        try {
            return getParent().getChildren().indexOf(this);
        }
        catch (Exception e) {
            return indexFromSet;
        }
    }
    
    
    int indexFromSet;
    
    
    private void setIndex(int index)
    {
        indexFromSet = index;
    }
    
This is ugly hack, and should not be used. Any better ideas ?
 
© Copyright 2006, Red Hat Middleware, LLC. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc. [Privacy Policy]