JPA 1-N/N-1 Relationships with Maps

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 Map of objects of another class. There are two general 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 Map. JPA only supports the Foreign-Key approach where you have the key stored as a field in the value.

The various possible relationships are described below.

This page is aimed at Map fields and so applies to fields of Java type java.util.HashMap, java.util.Hashtable, java.util.LinkedHashMap, java.util.Map, java.util.SortedMap, java.util.TreeMap, java.util.Properties



1-N Map using Foreign-Key
1-N Foreign-Key Unidirectional (key stored in value)

In this case we have an object with a Map of objects and we're associating the objects using a foreign-key in the table of the value. We're using a field ( alias ) in the Address class as the key of the map.

In this relationship, the Account class has a Map 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 MetaData like this

<entity-mappings>
    <entity class="Account">
        <table name="ACCOUNT"/>
        <attributes>
            <id name="id">
                <column name="ACCOUNT_ID"/>
            </id>
            ...
            <one-to-many name="addresses" target-entity="com.mydomain.Address">
                <map-key name="alias"/>
                <join-column name="ACCOUNT_ID_OID"/>
            </one-to-many>
        </attributes>
    </entity>

    <entity class="Address">
        <table name="ADDRESS"/>
        <attributes>
            <id name="id">
                <column name="ADDRESS_ID"/>
            </id>
            ...
            <basic name="alias">
                <column name="KEY" length="20"/>
            </basic>
        </attributes>
    </entity>
</entity-mappings>

Again there will be 2 tables, one for Address , and one for Account . 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 join-column element within the field of the map.





In terms of operation within your classes of assigning the objects in the relationship. You have to take your Account object and add the Address to the Account map field since the Address knows nothing about the Account . Also be aware that each Address object can have only one owner, since it has a single foreign key to the Account .



1-N Foreign-Key Bidirectional (key stored in value)

In this case we have an object with a Map of objects and we're associating the objects using a foreign-key in the table of the value.

With these classes we want to store a foreign-key in the value table (ADDRESS), and we want to use the "alias" field in the Address class as the key to the map. If you define the Meta-Data for these classes as follows

<entity-mappings>
    <entity class="Account">
        <table name="ACCOUNT"/>
        <attributes>
            <id name="id">
                <column name="ACCOUNT_ID"/>
            </id>
            ...
            <one-to-many name="addresses" target-entity="com.mydomain.Address" mapped-by="account">
                <map-key name="alias"/>
            </one-to-many>
        </attributes>
    </entity>

    <entity class="Address">
        <table name="ADDRESS"/>
        <attributes>
            <id name="id">
                <column name="ADDRESS_ID"/>
            </id>
            ...
            <basic name="alias">
                <column name="KEY" length="20"/>
            </basic>
            <many-to-one name="account">
                <join-column name="ACCOUNT_ID_OID"/>
            </many-to-one>
        </attributes>
    </entity>
</entity-mappings>

This will create 2 tables in the datastore. One for Account , and one for Address . The table for Address will contain the key field as well as an index to the Account record (notated by the mapped-by tag).