The following sections enumerate the myriad of field mappings JDOR
supports. JDOR augments standard persistence metadata's
field element with many new object-relational
attributes and sub-elements. As we explore the library of standard
mappings, we introduce each of these enhancements in context.

Note

Kodo allows you to create custom field mappings for unsupported
field types or database schemas. See the Reference Guide's
Chapter 7, Mapping for complete coverage of Kodo
JDO's mapping capabilities.

15.11.1. Superclass Fields

As you may have already noticed from the mapping document we have
been constructing throughout this chapter, subclasses can map
superclass fields by setting the field element's
name attribute to
<superclass-name>.<field-name>. For example,
Subscription maps its Contract
superclass' id field:

15.11.2. Basic Mapping

A basic field mapping stores the field value
directly into a database column. Primitives, primitive wrappers,
Strings, Dates,
Locales, and any other supported type that
can be easily translated into a native JDBC value default to
basic mapping. In fact, you have already seen examples of basic
field mappings in this chapter - for example, the mapping of
Magazine's primary key fields in
Example 15.3, “Datastore Identity Mapping”.

To write a basic field mapping, just name the column that holds
the field value. The field element accepts a
column attribute or nested column
elements for this purpose. We introduced columns and the
attribute/element dichotomy already in
Section 15.6, “Column”.

Note

Kodo stores Java 5 Enum values by
storing the value name in any CHAR or
VARCHAR column. Storing the name is safer
than storing the ordinal position, in case you reorder your
enum values or add new options at the beginning or middle of the
value list.

Below we present an updated diagram of our model and its associated
database schema, followed by its representation as a mapping
metadata document. All basic fields are now mapped.

15.11.2.1. CLOB

Unlike Java, most relational databases cannot transparently
store strings of arbitrary length. The standard database
string types CHAR and VARCHAR
are often limited to holding 255 characters or less.
To store longer strings, you must use the
CLOB column type.

CLOB stands for
Character Large OBject. In JDOR, mapping a Java
String field to a database CLOB
column is exactly like creating any other basic mapping.
The only difference is that you must use the column
element's jdbc-type attribute to
tell the JDOR implementation to use JDBC's CLOB
APIs rather than the standard string APIs.

Our sample model uses two large string fields:
Contract.terms and
LineItem.comments. Since Contract
uses a subclass-table mapping,
LineItem maps both these fields to its
own table:

In Kodo, you can set the column's
length attribute to -1
rather than setting its jdbc-type
to clob. This approach
allows Kodo to take advantage of some databases' ability to
represent unlimited-length strings natively, without
resorting to a CLOB. If your database
does not support unlimited-length strings natively, Kodo
falls back to CLOB handling.

Mapping a serialized Object field to a
BLOB column is like mapping a
byte[] field, except that you must
explicitly instruct JDOR to serialize the field value with the
serialized attribute. Suppose that instead
of a byte[], our
Article.content field is of type
Object, and we want to serialize that
Object to the CONTENT
column:

15.11.3. Automatic Values

In Section 15.5, “Datastore Identity”, you saw how to
use the datastore-identity element's
strategy and sequence attributes to
automatically generate datastore identity values. You can apply
the same pattern to auto-generate values for fields of
new objects. The field element's
value-strategy attribute accepts the same strategies as
datastore-identity's strategy
attribute. The field element also has a
sequence attribute that functions exactly like
datastore-identity's sequence
attribute. Let's modify our mappings to set
Article's id field automatically
from the ArticleSeq sequence, and to make the
LineNumber.num field auto-incrementing:

Kodo supports the standard sequence,
identity, autoassign,
uuid-string, and uuid-hex
field value strategies. Kodo also offers the custom
version strategy. Kodo will automatically
set a field with the version value strategy
to the object's optimistic version value. The field must be
a numeric or date type. If you use a version field, you should
not specify a separate version mapping (we covered version
mapping in Example 15.10, “Version Mapping”).

Kodo also defines a read-only metadata
extension that allows you to ignore or restrict updates to
a persistent field. See Section 6.4.2.7, “Read-Only” in
the Reference Guide for details. Fields that use the custom
version value strategy are always read-only.

Using the autoassign or identity
strategy on a field may cause the
PersistenceManager to
flush when retrieving the value of that field on a persistent-new
instance. If the field is a primary key field, retrieving the
object id of a persistent-new instance may also cause a flush, just
as it does for new instances using the autoassign
datastore identity strategy.
See Section 15.5, “Datastore Identity” for the
complete explanation.

15.11.4. Secondary Tables

Sometimes a a logical record is spread over multiple database
tables. JDOR calls a class' declared table the primary
table, and calls other tables that make up a logical
record secondary tables. You can map any
persistent field to a secondary table. Just write the standard
field mapping, then perform these two additional steps:

Set the field element's table
attribute to the name of the secondary table
housing the mapped columns.

Nest a join element within the
field that describes how the secondary
table joins to the class' primary table. The join
element goes before the field's nested
column elements, if any. We covered
joins in Section 15.7, “Joins”. In
fact, we already used the join element
to join a subclass table to its superclass table in
Section 15.8.2, “new-table”.

Secondary table joins, however, have one caveat that
subclass table joins do not. In a subclass join, you
know that there will always be a record in the superclass
table for every record in the subclass table. In a
secondary table join, that is not the case. Some databases
are maintained such that primary table rows may
not have matching secondary table rows. When you load
objects based on these records, you want the fields mapped
to the secondary table to come back null.

Joins that produce nulls for missing
records are called outer joins. You
may recall having read about another use for outer joins in
Section 15.9.3, “none”. If
your field is mapped to a secondary table that doesn't
necessarily have a row for every primary table row, set the
join element's outer
attribute to true.

Note

When the outer attribute is
true, Kodo will not insert null data rows
into the named secondary table. That is, if you are
inserting a new object, and it has null
values for every field mapped to an
outer-joined secondary table, Kodo will not insert a
row into that secondary table.

In the following example, we move the Article.content
field we mapped in
Section 15.11.2.2, “BLOB” into an outer-joined
secondary table, like so:

15.11.5. Direct Relations

A direct relation is a non-embedded persistent field that holds a
reference to another persistence-capable object. Our model
has three direct relations: Magazine's
publisher field is a direct relation
to a Company,
Magazine's coverArticle field is a
direct relation to Article, and
the LineItem.magazine field is a direct relation
to a Magazine. Direct relations are
represented in the database by foreign key columns:

A direct relation between types A and
B is also called a one-to-one
relation when every A has a
unique B. It is called a
many-to-one relation when multiple
As can refer to the same B.

15.11.5.1. Inverse Keys

The direct relations above are all based on forward
foreign keys. But relations can also be based on
inverse keys. We described inverse foreign
keys in Section 15.7, “Joins”.

For example, suppose that instead of Magazine
having a relation to its cover
Article, Article has a
relation to the Magazine it serves as
the cover of. We can represent this
relation without changing our schema:

Example 15.18. Inverse Key Relation Field Mapping

Notice that the Article.coverOf field
refers to the foreign key column in the
MAG table by using the fully qualified
column name.

15.11.5.2. Bidirectional Relations

In the previous sections, we saw that a single foreign key
from the MAG table to the
ART table can model an object relation from
Magazine to
Article (through the Magazine.coverArticle
field), or from Article to
Magazine (though the
Article.coverOf field). A natural question is whether
the foreign key can represent both relations at the same time.
The answer is yes.

When two fields are logical inverses of each other like
Magazine.coverArticle and
Article.coverOf, they form a
bidirectional relation. And when the two
fields of a bidirectional
relation share the same database mapping, JDOR formalizes the
connection with the mapped-by field
attribute. Using the mapped-by attribute,
we can map both Magazine.coverArticle and
Article.coverOf as follows:

Article.coverOf uses the foreign key
mapped by Magazine.coverArticle, but
inverses it. In fact, it is illegal to specify
any additional mapping information when you use the
mapped-by attribute. All mapping
information is read from the referenced field.

Magazine.coverArticle is the
"owner" of the relation. The field that specifies the
mapping data is always the owner. This means that
changes to the Magazine.coverArticle
field are reflected in the database, while changes to
the Article.coverOf field alone are
not. Changes to Article.coverOf may
still affect the JDOR implementation's cache, however.
Thus, it is very important that you keep your
object model consistent by properly maintaining both
sides of your bidirectional relations at all times.

Note

It is more efficient to make the field that
maps to the foreign key's natural forward
direction the owner of a bidirectional
relation. Specify the mapping data on the
forward foreign key side, and use
mapped-by on the inverse foreign key
side, just as we did with
Magazine.coverArticle and
Article.coverOf.

You should always take advantage of the mapped-by
attribute rather than mapping each field of a
bidirectional relation independently. Failing to do so may
result in the JDOR implementation trying to update the database
with conflicting data. Be careful to only mark one side of
the relation as mapped-by, however. One side
has to actually do the mapping!

Note

You can configure Kodo to automatically synchronize both
sides of a bidirectional relation, or to perform various
actions when it detects inconsistent relations. See
Section 5.4, “Managed Inverses” in the Reference Guide
for details.

15.11.6. Basic Collections

Collections and arrays of primitive wrappers,
Strings, Dates, or anything
else that can be directly stored in a database column all fall
under the umbrella of basic collection mapping. Basic collection
mapping is just a special case of secondary table mapping, as seen
in Section 15.11.4, “Secondary Tables”. Basic
collections map to a secondary table consisting of three
components:

Foreign key columns linking back to the owning class'
primary table. As with all secondary table mappings, these
foreign key columns are mapped to a join
element.

A column to hold a collection element. Each row of the
collection table holds a single collection or
array element. Logically enough, the element
element represents a collection or array element.

An optional ordering column. Relational databases do
not preserve record order. This column stores the
relative position of elements within the collection or
array so that the JDOR implementation can retrieve them
in the same order they were in when stored. The
order element anchors ordering column
information in mapping metadata.

Note

In addition to supporting an ordering column, Kodo allows you to
order on the collection element values, or, in the case of
relations, fields of the related type. See
Section 6.4.2.4, “Order-By” in the Reference Guide for details.

The Article.subtitles field is the only
basic collection in our model. This List
of Strings is mapped as follows:

15.11.7. Association Table Collections

An association table consists of two foreign
keys, plus an optional ordering column. In other words, if you
replace the data columns of a basic collection table
(Section 15.11.6, “Basic Collections”) with another
foreign key, you produce an association table. As its name implies,
each row of an association table associates two objects together.
JDOR uses association tables to represent collections of
persistence-capable objects: one foreign key refers back to the
collection's owner, and the other refers to a collection element.
Our model's Magazine.articles and
Company.subscriptions fields both have association table
mappings.

Note

An association table relation between types
A and B is also called a
one-to-many relation when every
A has a unique set of Bs.
It is called a many-to-many relation when
multiple As can refer to the same
B.

In metadata, an association table mapping is exactly like a basic
collection mapping
(Section 15.11.6, “Basic Collections”), except that
the element columns represent a foreign key.
You should be very familiar with foreign key mapping by now. See
Section 15.7, “Joins” for a refresher.

None of our model's association table collections are ordered,
but if they were, we could use the order
mapping element to specify the ordering column, just as in a
basic
collection mapping.

15.11.7.1. Bidirectional Relations

Association tables have no built-in sense of direction. Each
row links two objects, period. You can look at the
MAG_ARTS table in our example above as
linking each Magazine to its
Articles, or as linking each
Article to the Magazines it
appears in. The two outlooks are equally valid. This makes
association tables perfect candidates for representing
bidirectional relations.

Section 15.11.5.2, “Bidirectional Relations” examined direct
bidirectional relations that share a foreign key. Association
table bidirectional relations share association table rows
instead. Aside from that, all the same concepts apply.
In particular, you still use the mapped-by
attribute to mark one collection field as mapped and "owned"
by another collection field. The owning field is responsible
for manipulating association table records - the same caveats
about keeping your bidirectional relations synchronized apply
equally to collections.

In the example below, we have added an
Article.magazines field as the logical inverse of the
the Magazine.articles field.

15.11.8. Inverse Key Collections

Inverse key collection mappings are the only standard collection
mappings that do not use a secondary table. Instead, the mapping
is based on an inverse foreign key in the table of the related
class. Rather than try to explain the mapping abstractly, let's
jump to an example.

Note

Inverse key collection relations are also called
one-to-many relations.

The Subscription.items field in our model is
an inverse key collection. Subscription.items
is a Collection of LineItem
objects. There is a foreign key from
LineItem's table, CNTRCT.LINE_ITEM,
to Subscription's table,
CNTRCT.SUB. The relation is loaded by selecting
all LineItem records whose foreign key
links back to the owning Subscription's
record.

This situation may look familiar to you. It is, in fact, exactly
like the inverse key direct relation from
Section 15.11.5.1, “Inverse Keys”. The only thing
separating this mapping from the inverse key direct relation
mapping is that in this case, multiple rows in
CNTRCT.LINE_ITEM might have a foreign key to the same
CNTRCT.SUB record. At the object level, this
results in a collection rather than a direct reference.

This difference is also reflected in the mapping metadata. While
direct references are mapped to field,
collection elements are mapped to field's nested
element element. Also, you do not have to
fully qualify the name of the foreign key columns in an inverse
key collection, as you do in an inverse key direct relation.
That is because the lack of a secondary table is a hint to the
JDOR implementation that you are using an inverse key collection,
and it knows to look in the table of the related class
for the foreign key columns.

Though it is rare, you can map an order column to an inverse key
collection to maintain collection order. You use the
order mapping element, as in any other collection
mapping. The order column must be in the same table as the foreign
key column(s).

Note

Kodo allows you to specify an order column even in bidirectional
inverse key collection mappings.

15.11.8.1. Bidirectional Relations

Most inverse key collections are actually half of a
bidirectional relation. The other half is a standard
direct relation mapping
(Section 15.11.5, “Direct Relations”) onto the foreign
key. In fact, it is unusual to write an inverse key collection
mapping as we did above; the vast majority of inverse key
collections simply use the mapped-by
attribute introduced in
Section 15.11.5.2, “Bidirectional Relations” to refer to
their partner field. Our model's
Magazine.publisher and
Company.magazines fields are perfect illustrations
of the normal pairing between a direct relation and an inverse
key collection.

15.11.9. Maps

JDOR supports maps of all kinds. Your map keys can be basic types
like Strings, more interesting types like
BLOBs and CLOBs, or even persistence-capable objects. Map values
have equal flexibility. Rather than treat each combination of key
and value types as a separate mapping, this section explains the
concepts of map mapping in JDOR. Armed with a solid understanding
of map concepts, you will be able to write a mapping for any
combination of key and value classes, because object-relational
mapping in JDOR is remarkably consistent.

There are two ways to represent a map. The first is to write the
map to a secondary table. You may recall secondary table mapping
of singular values from
Section 15.11.4, “Secondary Tables”. In a map's
secondary table, each row represents a map entry. Therefore,
every map's secondary table has the following three components:

Foreign key columns linking back to the owning class'
primary table. As with all secondary table mappings, these
foreign key columns are mapped to a join
element.

Columns to hold the entry's key. If the map uses a simple
key type like String, these columns
will be standard data columns. If the map uses
persistence-capable objects as keys, these will be foreign
key columns linking to the records for those objects. Key
columns are anchored to to a key element
nested in the field element.

Columns to hold the entry's value. Like key columns, value
columns can be direct data columns or foreign key columns,
depending on the map's value type. You specify map value
columns on the field's value
element.

The second way to represent a map in JDOR is as a set of
persistence-capable values with derived keys. In the example
above, the key for each Author
is her last name. So rather than create a secondary table
with a dedicated key column, we can simply map the Author
values, then declare that the key is mapped by each
Author's last name. This lets us get rid
of the key column altogether.

When the key is derived from a field of the value type, you aren't
limited to using a secondary table to hold the map. In fact, you
can take any mapping that works for a collection of
persistence-capable objects and turn it into a map mapping in
two steps:

Add a key XML element. Set its
mapped-by attribute to the name of
a field in the persistence-capable value class. JDOR will
automatically map each entry on the value of this field.

Use the value XML element instead of the
element collection element to map the
related persistence-capable objects.

The diagram above depicts the mapping of a
Subscription's LineItems. We
created this mapping in
Section 15.11.8, “Inverse Key Collections”. The example
below turns this mapping into a map. Each map entry associates a
LineItem's num field value
to that LineItem.

15.11.10. Embedded Objects

Chapter 5, Metadata describes JDO's concept of
embedded objects. The field values of
embedded objects are stored as part of the owning record, rather
than as a separate database record. Thus, instead of mapping a
relation to an embedded object as a foreign key, you map all the
fields of the embedded instance to columns in the owning field's
table.

To perform an embedded mapping, nest the embedded
element in the embedded field. This element
has the following attributes:

null-indicator-column: Set this attribute
to the name of a column that indicates whether the embedded
instance is null. When the JDOR implementation retrieves
the embedded object, it will check this column first. If
the column value is null, the JDOR
runtime will load null into the field
that owns the embedded object. If the column value is not
null, the JDOR runtime will set the
owning field to a new embedded object instance, and will
load that instance's fields with data from their mapped
columns.

The null indicator column can be a column that is also
mapped to an embedded field, or can be an artificial column
whose sole purpose is to indicate whether the embedded
instance is null. In the latter case, the JDOR
implementation will set this column automatically.

If you do not specify a null indicator column, then an
embedded instance will never get loaded as
null. Even if you commit an owning object with
its embedded field set to null, the next
time you re-read the owning object its embedded field
will get loaded with a non-null embedded instance. The
embedded instance will have default values for all of its
fields.

Within the embedded element, nest
field elements for all the fields of the embedded
object. Map these fields as you would any other fields; you can
even map recursive embedded fields, to arbitrary depth.

In our model, Company embeds its
Address, as pictured in the diagram above.
Author also embeds its
Address. Let's see how this looks in mapping metadata:

Example 15.28. Embedded Field Mapping

This example uses the same column names for
Address's embedded fields in both the
COMP and AUTH tables.
Note, though, that nothing prevents you from using completely
different mappings each time you embed an object.

We set the null indicator column to STREET.
Any time the STREET column is
null, JDOR loads null into the
address field. Any time STREET
is not null, JDOR loads a new Address
instance into the address field,
and populates that Address with data
based on the above field mappings.

This section focused on fields that hold a direct reference to
an embedded object. However, JDOR also supports collections that
contain embedded object elements, and maps that contains embedded
object keys, values, or both. In these cases, the columns for the
embedded object's fields are part of the collection or map table.
Mapping embedded elements, keys, or values is exactly like mapping
an embedded field, except that instead of nesting the
embedded element under the
field element, you nest it under the
element, key, or value
elements, respectively. For example, if
Company had a collection of embedded addresses, like so: