|
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
In JPA1 all List relationships are "ordered Lists"
. If a List is an "ordered List"
then the positions of the elements in the List at persistence are not preserved (are not
persisted) and instead an ordering is defined for their retrieval.
In JPA2 Lists can optionally use a surrogate column to handle the ordering
.
This means that the positions of the elements in List at persistence are preserved. This
is the same situation as JDO provides.
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
<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">
<order-by>id</order-by>
<join-table name="ACCOUNT_ADDRESSES">
<join-column name="ACCOUNT_ID_OID"/>
<inverse-join-column name="ADDRESS_ID_EID"/>
</join-table>
</one-to-many>
</attributes>
</entity>
<entity class="Address">
<table name="ADDRESS"/>
<attributes>
<id name="id">
<column name="ADDRESS_ID"/>
</id>
...
</attributes>
</entity>
</entity-mappings>
|
The crucial part is the
join-table
element on the field element - this signals
to JPA to use a join table.
|
There will be 3 tables, one for
Address
, one for
Account
, and the join table.
This is identical to the handling for Sets/Collections.
Note that we specified <order-by> which defines the order the elements are
retrieved in (the "id" is the field in the List 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
element below the
class
element
-
To specify the names of the columns where the fields of a class are stored, specify
the
column
attribute on the
basic
element.
-
To specify the name of the join table, specify the
join-table
element below
the
one-to-many
element with the collection.
-
To specify the names of the join table columns, use the
join-column
and
inverse-join-column
elements below the
join-table
element.
-
The join table will NOT be given a primary key (so that duplicates can be stored).
-
If you want to have a surrogate column added to contain the ordering you
should specify
order-column
(@OrderColumn) instead of
order-by
.
This is available from JPA2

In strict JPA1 you cannot have a 1-N unidirectional relation using a ForeignKey -
they must have a JoinTable. Consequently to use this relation as specified below you
must specify the property "datanucleus.jpa.level" as "JPA2"/"DataNucleus"
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
<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">
<order-by>city</order-by>
<join-column name="ACCOUNT_ID"/>
</one-to-many>
</attributes>
</entity>
<entity class="Address">
<table name="ADDRESS"/>
<attributes>
<id name="id">
<column name="ADDRESS_ID"/>
</id>
...
</attributes>
</entity>
</entity-mappings>
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
element below the
class
element
-
To specify the names of the columns where the fields of a class are stored, specify
the
column
attribute on the
basic
element.
Limitations
-
If we are using an "ordered List" and the primary key of the join table contains
owner and element then duplicate elements can't be stored in a List. If you want to
allow duplicate List entries, then use the "Join Table" variant above.
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
<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">
<order-by>id</order-by>
<join-table name="ACCOUNT_ADDRESSES">
<join-column name="ACCOUNT_ID_OID"/>
<inverse-join-column name="ADDRESS_ID_EID"/>
</join-table>
</one-to-many>
</attributes>
</entity>
<entity class="Address">
<table name="ADDRESS"/>
<attributes>
<id name="id">
<column name="ADDRESS_ID"/>
</id>
...
<many-to-one name="account">
</many-to-one>
</attributes>
</entity>
</entity-mappings>
|
The crucial part is the
join
element on the field element - this signals to
JPA to use a join table.
|
This will create 3 tables in the database, one for
Address
,
one for
Account
, and a join table, as shown below.
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
element below the
class
element
-
To specify the names of the columns where the fields of a class are stored, specify
the
column
attribute on the
basic
element.
-
To specify the name of the join table, specify the
join-table
element below
the
one-to-many
element with the collection.
-
To specify the names of the join table columns, use the
join-column
and
inverse-join-column
elements below the
join-table
element.
-
The join table will NOT be given a primary key (so that duplicates can be stored).
-
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 to have a surrogate column added to contain the ordering you
should specify
order-column
(@OrderColumn) instead of
order-by
.
This is available from JPA2
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
<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">
<order-by>city ASC</order-by>
<join-column name="ACCOUNT_ID"/>
</one-to-many>
</attributes>
</entity>
<entity class="Address">
<table name="ADDRESS"/>
<attributes>
<id name="id">
<column name="ADDRESS_ID"/>
</id>
...
<many-to-one name="account">
</many-to-one>
</attributes>
</entity>
</entity-mappings>
|
The crucial part is the
mapped-by
attribute of the field on the "1" side of
the relationship. This tells the JPA implementation to look for a field called
account
on the
Address
class.
|
This will create 2 tables in the database, one for
Address
(including an
ACCOUNT_ID
to link to the
ACCOUNT
table), and
one for
Account
. Notice the subtle difference to this set-up to that of the
Join Table
relationship earlier.
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
element below the
class
element
-
To specify the names of the columns where the fields of a class are stored, specify
the
column
attribute on the
basic
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 Collection.
If you want to allow duplicate Collection entries, then use the "Join Table" variant
above.
In JPA1 you cannot have a 1-N List of non-Entity objects. This is available in JPA2.
All of the examples above show a 1-N relationship between 2
PersistenceCapable
classes.
If you want the element to be primitive or Object types then follow this section. 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 collection elements.
Let's take our example. We have an
Account
that stores a Collection of addresses.
These addresses are simply Strings. We define the annotations like this
@Entity
public class Account
{
...
@ElementCollection(targetClass=String.class)
@CollectionTable(name="ACCOUNT_ADDRESSES")
Collection addresses;
}
In the datastore the following is created
The ACCOUNT table is as before, but this time we only have the "join table".
Use @Column on the field/method to define the column details of the element in the join table.
|
|