JDO : 1-N Relationships with Lists

You have a 1-N (one to many) or N-1 (many to one) when you have one object of a class that has a List of objects of another class. There are two ways in which you can represent this in a datastore. Join Table (where a join table is used to provide the relationship mapping between the objects), and Foreign-Key (where a foreign key is placed in the table of the object contained in the List.

The various possible relationships are described below.

This page is aimed at List fields and so applies to fields of Java type java.util.ArrayList, java.util.LinkedList, java.util.List, java.util.Stack, java.util.Vector

Please note that RDBMS supports the full range of options on this page, whereas other datastores (ODF, Excel, HBase, MongoDB, etc) persist the List in a column in the owner object (as well as a column in the non-owner object when bidirectional) rather than using join-tables or foreign-keys since those concepts are RDBMS-only.

equals() and hashCode()

Important : The element of a Collection ought to define the methods equals and hashCode so that updates are detected correctly. This is because any Java Collection will use these to determine equality and whether an element is contained in the Collection. Note also that the hashCode() should be consistent throughout the lifetime of a persistable object. By that we mean that it should not use some basis before persistence and then use some other basis (such as the object identity) after persistence, for this reason we do not recommend usage of JDOHelper.getObjectId(obj) in the equals/hashCode methods.

1-N List Unidirectional

We have 2 sample classes Account and Address. These are related in such a way as Account contains a List of objects of type Address, yet each Address knows nothing about the Account objects that it relates to. Like this

There are 2 ways that we can persist this relationship. These are shown below

Using Join Table

If you define the XML metadata for these classes as follows

<package name="com.mydomain">
    <class name="Account" identity-type="datastore">
        ...
        <field name="addresses">
            <collection element-type="com.mydomain.Address"/>
            <join/>
        </field>
    </class>

    <class name="Address" identity-type="datastore">
        ...
    </class>
</package>

or alternatively using annotations

public class Account
{
    ...

    @Join
    List<Address> addresses;
}

public class Address
{
    ...
}
The crucial part is the join element on the field element - this signals to JDO to use a join table.

There will be 3 tables, one for Address, one for Account, and the join table. The difference from Set is in the contents of the join table. An index column (INTEGER_IDX) is added to keep track of the position of objects in the List. The name of this column can be controlled using the <order> MetaData element.



The join table is used to link the 2 classes via foreign keys to their primary key. This is useful where you want to retain the independence of one class from the other class.

If you wish to fully define the schema table and column names etc, follow these tips

  • To specify the name of the table where a class is stored, specify the table attribute on the class element
  • To specify the names of the columns where the fields of a class are stored, specify the column attribute on the field element.
  • To specify the name of the join table, specify the table attribute on the field element with the collection.
  • To specify the names of the join table columns, use the column attribute of join, element and order elements.
  • To specify the foreign-key between container table and join table, specify <foreign-key> below the <join> element.
  • To specify the foreign-key between join table and element table, specify <foreign-key> below either the <field> element or the <element> element.
  • If you wish to share the join table with another relation then use the DataNucleus "shared join table" extension
  • The join table will, by default, be given a primary key. If you want to omit this then you can turn it off using the DataNucleus metadata extension "primary-key" (within <join>) set to false.
  • The column "ADPT_PK_IDX" is added by DataNucleus so that duplicates can be stored. You can control this by adding an <order> element and specifying the column name for the order column, or you can override the default naming of this column by specifying the DataNucleus extension "adapter-column-name" (within <field>).
  • If you want the set to include nulls, you can turn on this behaviour by adding the extension metadata "allow-nulls" to the <field> set to true

Using Foreign-Key

In this relationship, the Account class has a List of Address objects, yet the Address knows nothing about the Account. In this case we don't have a field in the Address to link back to the Account and so DataNucleus has to use columns in the datastore representation of the Address class. So we define the XML metadata like this

<package name="com.mydomain">
    <class name="Account" identity-type="datastore">
        ...
        <field name="addresses">
            <collection element-type="com.mydomain.Address"/>
            <element column="ACCOUNT_ID"/>
        </field>
    </class>

    <class name="Address" identity-type="datastore">
        ...
    </class>
</package>

or alternatively using annotations

public class Account
{
    ...

    @Element(column="ACCOUNT_ID")
    List<Address> addresses;
}

public class Address
{
    ...
}

Again there will be 2 tables, one for Address, and one for Account. Note that we have no "mapped-by" attribute specified, and also no "join" element. If you wish to specify the names of the columns used in the schema for the foreign key in the Address table you should use the element element within the field of the collection.



In terms of operation within your classes of assigning the objects in the relationship. With DataNucleus and List-based containers you have to take your Account object and add the Address to the Account collection field since the Address knows nothing about the Account.

If you wish to fully define the schema table and column names etc, follow these tips

  • To specify the name of the table where a class is stored, specify the table attribute on the class element
  • To specify the names of the columns where the fields of a class are stored, specify the column attribute on the field element.
  • To specify the foreign-key between container table and element table, specify <foreign-key> below either the <field> element or the <element> element.

Limitations

  • Since each Address object can have at most one owner (due to the "Foreign Key") this mode of persistence will not allow duplicate values in the List. If you want to allow duplicate List entries, then use the "Join Table" variant above.

1-N Ordered List using Foreign-Key

This is the same as the case above except that we don't want an indexing column adding to the element and instead we define an "ordering" criteria. This is a DataNucleus extension to JDO. So we define the XML metadata like this

<package name="com.mydomain">
    <class name="Account" identity-type="datastore">
        ...
        <field name="addresses">
            <collection element-type="com.mydomain.Address"/>
            <order>
                <extension vendor-name="datanucleus" key="list-ordering" value="city ASC"/>
            </order>
        </field>
    </class>

    <class name="Address" identity-type="datastore">
        ...
    </class>
</package>

or alternatively using annotations

public class Account
{
    ...

    @Order(extensions=@Extension(vendorName="datanucleus", key="list-ordering", value="city ASC"))
    List<Address> addresses;
}

public class Address
{
    ...
}

As above there will be 2 tables, one for Address, and one for Account. We have no indexing column, but instead we will order the elements using the "city" field in ascending order.



In terms of operation within your classes of assigning the objects in the relationship. With DataNucleus and List-based containers you have to take your Account object and add the Address to the Account collection field since the Address knows nothing about the Account.

Limitations

  • Ordered lists are only ordered in the defined way when retrieved from the datastore.

1-N List Bidirectional

We have 2 sample classes Account and Address. These are related in such a way as Account contains a List of objects of type Address, and each Address has a reference to the Account object that it relates to. Like this

There are 2 ways that we can persist this relationship. These are shown below

Using Join Table

If you define the XML metadata for these classes as follows

<package name="com.mydomain">
    <class name="Account" identity-type="datastore">
        ...
        <field name="addresses" mapped-by="account">
            <collection element-type="com.mydomain.Address"/>
            <join/>
        </field>
    </class>

    <class name="Address" identity-type="datastore">
        ...
        <field name="account"/>
    </class>
</package>

or alternatively using annotations

public class Account
{
    ...

    @Persistent(mappedBy="account")
    @Join
    List<Address> addresses;
}

public class Address
{
    ...

    Account account;
}
The crucial part is the join element on the field element - this signals to JDO to use a join table.

There will be 3 tables, one for Address, one for Account, and the join table. The difference from Set is in the contents of the join table. An index column (INTEGER_IDX) is added to keep track of the position of objects in the List. The name of this column can be controlled using the <order> MetaData element.



The join table is used to link the 2 classes via foreign keys to their primary key. This is useful where you want to retain the independence of one class from the other class.

If you wish to fully define the schema table and column names etc, follow these tips

  • To specify the name of the table where a class is stored, specify the table attribute on the class element
  • To specify the names of the columns where the fields of a class are stored, specify the column attribute on the field element.
  • To specify the name of the join table, specify the table attribute on the field element with the collection.
  • To specify the names of the join table columns, use the column attribute of join, element and order elements.
  • To specify the foreign-key between container table and join table, specify <foreign-key> below the <join> element.
  • To specify the foreign-key between join table and element table, specify <foreign-key> below either the <field> element or the <element> element.
  • If you wish to share the join table with another relation then use the DataNucleus "shared join table" extension
  • The join table will, by default, be given a primary key. If you want to omit this then you can turn it off using the DataNucleus metadata extension "primary-key" (within <join>) set to false.
  • The column "ADPT_PK_IDX" is added by DataNucleus so that duplicates can be stored. You can control this by adding an <order> element and specifying the column name for the order column, or you can override the default naming of this column by specifying the DataNucleus extension "adapter-column-name" (within <field>).
  • When forming the relation please make sure that you set the relation at BOTH sides since DataNucleus would have no way of knowing which end is correct if you only set one end.
  • If you want the set to include nulls, you can turn on this behaviour by adding the extension metadata "allow-nulls" to the <field> set to true

Using Foreign-Key

Here we have the 2 classes with both knowing about the relationship with the other.

Please note that an Foreign-Key List will NOT, by default, allow duplicates. This is because it stores the element position in the element table. If you need a List with duplicates we recommend that you use the Join Table List implementation above. If you have an application identity element class then you could in principle add the element position to the primary key to allow duplicates, but this would imply changing your element class identity.

If you define the Meta-Data for these classes as follows

<package name="com.mydomain">
    <class name="Account" identity-type="datastore">
        ...
        <field name="addresses" mapped-by="account">
            <collection element-type="com.mydomain.Address"/>
        </field>
    </class>

    <class name="Address" identity-type="datastore">
        ...
        <field name="account">
            <column name="ACCOUNT_ID"/>
        </field>
    </class>
</package>

or alternatively using annotations

public class Account
{
    ...

    @Persistent(mappedBy="account")
    List<Address> addresses;
}

public class Address
{
    ...

    Account account;
}
The crucial part is the mapped-by attribute of the field on the "1" side of the relationship. This tells the JDO implementation to look for a field called account on the Address class.

Again there will be 2 tables, one for Address, and one for Account. The difference from the Set example is that the List index is placed in the table for Address whereas for a Set this is not needed.



In terms of operation within your classes of assigning the objects in the relationship. With DataNucleus and List-based containers you have to take your Account object and add the Address to the Account collection field (you can't just take the Address object and set its Account field since the position of the Address in the List needs setting, and this is done by adding the Address to the Account). In addition, if you are removing an object from a List, you cannot simply set the owner on the element to "null". You have to remove it from the List end of the relationship.

If you wish to fully define the schema table and column names etc, follow these tips

  • To specify the name of the table where a class is stored, specify the table attribute on the class element
  • To specify the names of the columns where the fields of a class are stored, specify the column attribute on the field element.
  • To specify the foreign-key between container table and element table, specify <foreign-key> below either the <field> element or the <element> element.
  • When forming the relation please make sure that you set the relation at BOTH sides since DataNucleus would have no way of knowing which end is correct if you only set one end.

Limitation : Since each Address object can have at most one owner (due to the "Foreign Key") this mode of persistence will not allow duplicate values in the List. If you want to allow duplicate List entries, then use the "Join Table" variant above.


1-N List of non-PersistenceCapable objects

All of the examples above show a 1-N relationship between 2 PersistenceCapable classes. DataNucleus can also cater for a List of primitive or Object types. For example, when you have a List of Strings. This will be persisted in the same way as the "Join Table" examples above. A join table is created to hold the list elements. Let's take our example. We have an Account that stores a List of addresses. These addresses are simply Strings. We define the XML metadata like this

<package name="com.mydomain">
    <class name="Account" identity-type="datastore">
        ...
        <field name="addresses" persistence-modifier="persistent">
            <collection element-type="java.lang.String"/>
            <join/>
            <element column="ADDRESS"/>
        </field>
    </class>

or alternatively using annotations

public class Account
{
    ...

    @Join
    @Element(column="ADDRESS")
    List<String> addresses;
}

In the datastore the following is created

The ACCOUNT table is as before, but this time we only have the "join table". In our MetaData we used the <element> tag to specify the column name to use for the actual address String. In addition we have an additional index column to form part of the primary key (along with the FK back to the ACCOUNT table). You can override the default naming of this column by specifying the <order> tag.

Embedded into a Join Table

The above relationship types assume that both classes in the 1-N relation will have their own table. A variation on this is where you have a join table but you embed the elements of the collection into this join table. To do this you use the embedded-element attribute on the collection MetaData element. This is described in Embedded Collection Elements.

Serialised into a Join Table

The above relationship types assume that both classes in the 1-N relation will have their own table. A variation on this is where you have a join table but you serialise the elements of the collection into this join table in a single column. To do this you use the serialised-element attribute on the collection MetaData element. This is described in Serialised Collection Elements