|
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
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
If you define the Meta-Data for these classes as follows
<package name="com.mydomain">
<class name="Account" identity-type="datastore">
...
<field name="addresses" persistence-modifier="persistent">
<collection element-type="com.mydomain.Address"/>
<join/>
</field>
</class>
<class name="Address" identity-type="datastore">
...
</class>
</package>
|
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
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 MetaData like this
<package name="com.mydomain">
<class name="Account" identity-type="datastore">
...
<field name="addresses" persistence-modifier="persistent">
<collection element-type="com.mydomain.Address"/>
<element column="ACCOUNT_ID"/>
</field>
</class>
<class name="Address" identity-type="datastore">
...
</class>
</package>
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.
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 MetaData like this
<package name="com.mydomain">
<class name="Account" identity-type="datastore">
...
<field name="addresses" persistence-modifier="persistent">
<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>
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.
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
If you define the Meta-Data for these classes as follows
<package name="com.mydomain">
<class name="Account" identity-type="datastore">
...
<field name="addresses" persistence-modifier="persistent" mapped-by="account">
<collection element-type="com.mydomain.Address"/>
<join/>
</field>
</class>
<class name="Address" identity-type="datastore">
...
<field name="account" persistence-modifier="persistent"/>
</class>
</package>
|
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
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" persistence-modifier="persistent" mapped-by="account">
<collection element-type="com.mydomain.Address"/>
</field>
</class>
<class name="Address" identity-type="datastore">
...
<field name="account" persistence-modifier="persistent">
<column name="ACCOUNT_ID"/>
</field>
</class>
</package>
|
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.
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 Meta-Data 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>
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.
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.
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
|
|