15.11. Field Mapping

15.11.1. Superclass Fields
15.11.2. Basic Mapping
15.11.2.1. CLOB
15.11.2.2. BLOB
15.11.3. Automatic Values
15.11.4. Secondary Tables
15.11.5. Direct Relations
15.11.5.1. Inverse Keys
15.11.5.2. Bidirectional Relations
15.11.6. Basic Collections
15.11.7. Association Table Collections
15.11.7.1. Bidirectional Relations
15.11.8. Inverse Key Collections
15.11.8.1. Bidirectional Relations
15.11.9. Maps
15.11.10. Embedded Objects

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]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:

<class name="Subscription" table="CNTRCT.SUB">
    <field name="Contract.id" column="ID"/>
    ...
</class> 

If Contract were in a different package than Subscription, we would have to fully qualify Contract's class name:

<class name="Subscription" table="CNTRCT.SUB">
    <field name="org.mag.subscribe.Contract.id" column="ID"/>
    ...
</class> 

As we tour the standard JDOR field mappings throughout this chapter, keep in mind that any of them can be applied to a superclass field just as easily as to a field in the current class.

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]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.

Example 15.11. Basic Field Mapping

<?xml version="1.0"?>
<orm>
    <package name="org.mag">
        <class name="Magazine" table="MAG">
            <field name="isbn">
                <column name="ISBN" jdbc-type="char" length="15"/>
            </field>
            <field name="title" column="TITLE"/>
            <field name="price" column="PRICE"/>
            <field name="copiesSold" column="COPIES"/>
            ...
        </class>
        <class name="Article" table="ART">
            <field name="id" column="ID"/>
            <field name="title" column="TITLE"/>
            ...
        </class>    
    </package>
    <package name="org.mag.pub">
        <class name="Company" table="COMP">
            <field name="name" column="NAME"/>
            <field name="revenue" column="REV"/>
            ...
        </class>    
        <class name="Author" table="AUTH">
            <field name="firstName" column="FNAME"/>
            <field name="lastName" column="LNAME"/>
            ...
        </class>
    </package>
    <package name="org.mag.subscribe">
        <class name="Subscription" table="CNTRCT.SUB">
            <field name="Contract.id" column="ID"/>
            <field name="startDate" column="START"/>
            <field name="payment" column="PAY"/>
            ...
        </class>
        <class name="LifetimeSubscription">
            <field name="eliteClub" column="ELITE"/>
            ...
        </class>
        <class name="TrialSubscription" table="CNTRCT.TRIAL_SUB">
            <field name="endDate" column="END"/>
            ...
        </class>
        <class name="Subscription$LineItem" table="CNTRCT.LINE_ITEM">
            <field name="Contract.id" column="ID"/>
            <field name="price" column="PRICE"/>
            <field name="num" column="NUM"/>
            ...
        </class>
    </package>
</orm>

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 mapping metadata, CLOB mapping looks like this:

Example 15.12. CLOB Field Mapping

<class name="Subscription$LineItem" table="CNTRCT.LINE_ITEM">
    <field name="Contract.terms">
        <column name="TERMS" jdbc-type="clob"/>
    </field>
    <field name="comments">
        <column name="COMMENTS" jdbc-type="clob"/>
    </field>
    ...
</class>
[Note]Note

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.

15.11.2.2. BLOB

BLOBs are Binary Large OBjects. JDOR uses BLOB columns to store byte arrays and serialized Java objects.

Mapping a byte[] field to a BLOB column is a basic mapping. For example, let's map our model's Article.content field to the ART.CONTENT BLOB column:

Example 15.13. Byte Array Field Mapping

<class name="Article" table="ART">
    <field name="content" column="CONTENT"/>
    ...
</class>

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:

Example 15.14. Serialized Field Mapping

<class name="Article" table="ART">
    <field name="content" column="CONTENT" serialized="true"/>
    ...
</class>

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:

Example 15.15. Automatic Field Values

<?xml version="1.0"?>
<orm>
    <package name="org.mag">
        <sequence name="ArticleSeq" datastore-sequence="ART_SEQ"/> 
        <class name="Article" table="ART">
            <!-- specifying the sequence attribute defaults the -->
            <!-- value-strategy attribute to "sequence" automatically -->
            <field name="id" column="ID" sequence="ArticleSeq"/>
            ...
        </class>    
        ...
    </package>
    <package name="org.mag.subscribe">
        <class name="Subscription$LineItem" table="CNTRCT.LINE_ITEM">
            <field name="num" column="NUM" value-strategy="autoassign"/>
            ...
        </class>
        ...
    </package>
</orm>
[Note]Note

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:

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

  2. 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]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:

Example 15.16. Secondary Table Field Mapping

<class name="Article" table="ART">
    <field name="id" column="ID"/>
    <field name="content" table="ART_DATA" column="CONTENT">
        <join outer="true">
            <column name="ART_ID" target="ID"/>
        </join>
    </field>
    ...
</class>

Because the target table ART only has a single primary key column, we can use join shortcuts to get rid of the nested column element. Our final mapping becomes:

<class name="Article" table="ART">
    <field name="id" column="ID"/>
    <field name="content" table="ART_DATA" column="CONTENT">
        <join outer="true" column="ART_ID"/>
    </field>
    ...
</class>

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:

You should be familiar with foreign key mapping from Section 15.7, “Joins”. Additionally, we have already demonstrated mapping foreign key columns onto the join element in Section 15.8.2, “new-table” and Section 15.11.4, “Secondary Tables”. The only difference now is that direct relations map the foreign key columns directly onto the field element:

Example 15.17. Direct Relation Field Mapping

<?xml version="1.0"?>
<orm>
    <package name="org.mag">
        <class name="Magazine" table="MAG">
            <field name="isbn">
                <column name="ISBN" jdbc-type="char" length="15"/>
            </field>
            <field name="title" column="TITLE"/>
            <field name="coverArticle">
                <column name="COVER_ID" target="ID"/>
            </field>
            <field name="publisher">
                <column name="PUB_ID" target="CID"/>
            </field>
            ...
        </class>
        <class name="Article" table="ART">
            <field name="id" column="ID"/>
            ...
        </class>    
        ...
    </package>
    <package name="org.mag.pub">
        <class name="Company" table="COMP">
            <datastore-identity column="CID"/>
            ...
        </class>    
        ...
    </package>
    <package name="org.mag.subscribe">
        <class name="Subscription$LineItem" table="CNTRCT.LINE_ITEM">
            <field name="magazine">
                <column name="MAG_ISBN" target="ISBN"/>
                <column name="MAG_TITLE" target="TITLE"/>
            </field>
            ...
        </class>
        ...
    </package>
</orm>

Or, after applying syntactic shortcuts:

<?xml version="1.0"?>
<orm>
    <package name="org.mag">
        <class name="Magazine" table="MAG">
            <field name="isbn">
                <column name="ISBN" jdbc-type="char" length="15"/>
            </field>
            <field name="title" column="TITLE"/>
            <field name="coverArticle" column="COVER_ID"/>
            <field name="publisher" column="PUB_ID"/>
            ...
        </class>
        <class name="Article" table="ART">
            <field name="id" column="ID"/>
            ...
        </class>    
        ...
    </package>
    <package name="org.mag.pub">
        <class name="Company" table="COMP">
            <datastore-identity column="CID"/>
            ...
        </class>    
        ...
    </package>
    <package name="org.mag.subscribe">
        <class name="Subscription$LineItem" table="CNTRCT.LINE_ITEM">
            <field name="magazine">
                <column name="MAG_ISBN" target="ISBN"/>
                <column name="MAG_TITLE" target="TITLE"/>
            </field>
            ...
        </class>
        ...
    </package>
</orm>
[Note]Note

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.

<class name="Magazine" table="MAG">
    ...
</class>
<class name="Article" table="ART">
    <field name="id" column="ID"/>
    <field name="coverOf">
        <column name="MAG.PUB_ID" target="ID"/>
    </field>
    ...
</class>

After syntactic shortuts:

<class name="Magazine" table="MAG">
    ...
</class>
<class name="Article" table="ART">
    <field name="id" column="ID"/>
    <field name="coverOf" column="MAG.PUB_ID"/>
    ...
</class>

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:

Example 15.19. Mapping a Bidirectional Relation

<class name="Magazine" table="MAG">
    <field name="coverArticle" column="PUB_ID"/>
    ...
</class>
<class name="Article" table="ART">
    <field name="coverOf" mapped-by="coverArticle"/>
    ...
</class>

Marking Article.coverOf as mapped-by Magazine.coverArticle means two things:

  1. 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.

  2. 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]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]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:

  1. 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.

  2. 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.

  3. 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]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:

In mapping metadata, this becomes:

Example 15.20. Basic Collection Field Mapping

<class name="Article" table="ART">
    <field name="id" column="ID"/>
    <field name="subtitles" table="ART_SUBS">
        <join>
            <column name="ART_ID" target="ID"/>
        </join>
        <element column="SUBTITLE"/>
        <order column="ORD"/>
    </field>
    ...
</class>

Taking advantage of join shortuts yields:

<class name="Article" table="ART">
    <field name="id" column="ID"/>
    <field name="subtitles" table="ART_SUBS">
        <join column="ART_ID"/>
        <element column="SUBTITLE"/>
        <order column="ORD"/>
    </field>
    ...
</class>

The same pattern applies to mapping collections of BLOBs or CLOBs, or any elements that can be stored in one or more data columns.

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]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.

Example 15.21. Association Table Collection Field Mapping

<?xml version="1.0"?>
<orm>
    <package name="org.mag">
        <class name="Magazine" table="MAG">
            <field name="isbn">
                <column name="ISBN" jdbc-type="char" length="15"/>
            </field>
            <field name="title" column="TITLE"/>
            <field name="articles" table="MAG_ARGS">
                <join>
                    <column name="MAG_ISBN" target="ISBN"/>
                    <column name="MAG_TITLE" target="TITLE"/>
                </join>
                <element>
                    <column name="ART_ID" target="ID"/>
                </element>
            </field>
            ...
        </class>
        <class name="Article" table="ART">
            <field name="id" column="ID"/>
            ...
        </class>    
        ...
    </package>
    <package name="org.mag.pub">
        <class name="Company" table="COMP">
            <datastore-identity column="CID" strategy="autoassign"/>
            <field name="subscriptions" table="COMP_SUBS">
                <join>
                    <column name="COMP_ID" target="CID"/>
                </join>
                <element>
                    <column name="SUB_ID" target="ID"/>
                </element>
            </field>
            ...
        </class>    
        ...
    </package>
    <package name="org.mag.subscribe">
        <class name="Subscription" table="CNTRCT.SUB">
            <field name="Contract.id" column="ID"/>
            ...
        </class>
        ...
    </package>
</orm>

Applying join shortcuts yields:

<?xml version="1.0"?>
<orm>
    <package name="org.mag">
        <class name="Magazine" table="MAG">
            <field name="isbn">
                <column name="ISBN" jdbc-type="char" length="15"/>
            </field>
            <field name="title" column="TITLE"/>
            <field name="articles" table="MAG_ARGS">
                <join>
                    <column name="MAG_ISBN" target="ISBN"/>
                    <column name="MAG_TITLE" target="TITLE"/>
                </join>
                <element column="ART_ID"/>
            </field>
            ...
        </class>
        <class name="Article" table="ART">
            <field name="id" column="ID"/>
            ...
        </class>    
        ...
    </package>
    <package name="org.mag.pub">
        <class name="Company" table="COMP">
            <datastore-identity column="CID" strategy="autoassign"/>
            <field name="subscriptions" table="COMP_SUBS">
                <join column="COMP_ID"/>
                <element column="SUB_ID"/>
            </field>
            ...
        </class>    
        ...
    </package>
    <package name="org.mag.subscribe">
        <class name="Subscription" table="CNTRCT.SUB">
            <field name="Contract.id" column="ID"/>
            ...
        </class>
        ...
    </package>
</orm>

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.

Example 15.22.  Mapping Bidirectional Association Table Relations

<?xml version="1.0"?>
<orm>
    <package name="org.mag">
        <class name="Magazine" table="MAG">
            <field name="isbn">
                <column name="ISBN" jdbc-type="char" length="15"/>
            </field>
            <field name="title" column="TITLE"/>
            <field name="articles" table="MAG_ARGS">
                <join>
                    <column name="MAG_ISBN" target="ISBN"/>
                    <column name="MAG_TITLE" target="TITLE"/>
                </join>
                <element column="ART_ID"/>
            </field>
            ...
        </class>
        <class name="Article" table="ART">
            <field name="id" column="ID"/>
            <field name="magazines" mapped-by="articles"/>
            ...
        </class>    
        ...
    </package>
</orm>
[Note]Note

Association tables are symmetric; therefore, the choice of owning vs. non-owning side is arbitrary. However, only the owning side of the relation can order the its collection elements.

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]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.

Example 15.23. Inverse Key Collection Field Mapping

<?xml version="1.0"?>
<orm>
    <package name="org.mag.subscribe">
        <class name="Subscription" table="CNTRCT.SUB">
            <field name="Contract.id" column="ID"/>
            <field name="items">
                <element>
                    <column name="SUB_ID" target="ID"/>
                </element>
            </field>
            ...
        </class>
        <class name="Subscription$LineItem" table="CNTRCT.LINE_ITEM">
            <field name="Contract.id" column="ID"/>
            ...
        </class>
        ...
    </package>
</orm>

Or with join shortcuts in place:

<?xml version="1.0"?>
<orm>
    <package name="org.mag.subscribe">
        <class name="Subscription" table="CNTRCT.SUB">
            <field name="Contract.id" column="ID"/>
            <field name="items">
                <element column="SUB_ID"/>
            </field>
            ...
        </class>
        <class name="Subscription$LineItem" table="CNTRCT.LINE_ITEM">
            <field name="Contract.id" column="ID"/>
            ...
        </class>
        ...
    </package>
</orm>

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]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.

Example 15.24. Direct Relation / Inverse Key Collection Pair

<?xml version="1.0"?>
<orm>
    <package name="org.mag">
        <class name="Magazine" table="MAG">
            <field name="publisher" column="PUB_ID"/>
            ...
        </class>
        ...
    </package>
    <package name="org.mag.pub">
        <class name="Company" table="COMP">
            <datastore-identity column="CID"/>
            <field name="mags" mapped-by="publisher"/>
            ...
        </class>    
        ...
    </package>
</orm>

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:

  1. 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.

  2. 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.

  3. 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.

In short, secondary table map mapping is like a combination of basic collection mapping (Section 15.11.6, “Basic Collections”) and association table collection mapping (Section 15.11.7, “Association Table Collections”), depending on the key and value types involved.

Article.authors is the lone map field in our model. For each author of the article, this field maps the author's last name to the persistent Author instance.

To translate this to mapping metadata, we perform a basic secondary table mapping, then add the key columns to the key element and the value foreign key columns to the value element.

Example 15.25. Map Field Mapping

<?xml version="1.0"?>
<orm>
    <package name="org.mag">
        <class name="Article" table="ART">
            <field name="id" column="ID"/>
            <field name="authors" table="ART_AUTHS">
                <join>
                    <column name="ART_ID" target="ID"/>
                </join>
                <key column="LNAME"/>
                <value>
                    <column name="AUTH_ID" target="AID"/>
                </value>
            </field>
            ...
        </class>    
        ...
    </package>
    <package name="org.mag.pub">
        <class name="Author" table="AUTH">
            <datastore-identity sequence="AuthorSeq">
                <column name="AID" sql-type="INTEGER64"/>
            </datastore-identity>
            ...
        </class>
        ...
    </package>
</orm>

Or with join shortuts in place:

<?xml version="1.0"?>
<orm>
    <package name="org.mag">
        <class name="Article" table="ART">
            <field name="id" column="ID"/>
            <field name="authors" table="ART_AUTHS">
                <join column="ART_ID"/>
                <key column="LNAME"/>
                <value column="AUTH_ID"/>
            </field>
            ...
        </class>    
        ...
    </package>
    <package name="org.mag.pub">
        <class name="Author" table="AUTH">
            <datastore-identity>
                <column name="AID" sql-type="INTEGER64"/>
            </datastore-identity>
            ...
        </class>
        ...
    </package>
</orm>

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.

Example 15.26. Derived Key Mapping

<?xml version="1.0"?>
<orm>
    <package name="org.mag">
        <class name="Article" table="ART">
            <field name="id" column="ID"/>
            <field name="authors" table="ART_AUTHS">
                <join column="ART_ID"/>
                <key mapped-by="lastName"/>
                <value column="AUTH_ID"/>
            </field>
            ...
        </class>    
        ...
    </package>
    <package name="org.mag.pub">
        <class name="Author" table="AUTH">
            <datastore-identity>
                <column name="AID" sql-type="INTEGER64"/>
            </datastore-identity>
            ...
        </class>
        ...
    </package>
</orm>

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:

  1. 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.

  2. 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.

Example 15.27. Inverse Foreign Key Map Mapping


public class Subscription
    extends Contract
{
    private Map<Long,LineItem> lineItems;
    ...
}

<?xml version="1.0"?>
<orm>
    <package name="org.mag.subscribe">
        <class name="Subscription" table="CNTRCT.SUB">
            <field name="Contract.id" column="ID"/>
            <field name="items">
                <key mapped-by="num"/>
                <value column="SUB_ID"/>
            </field>
            ...
        </class>
        <class name="Subscription$LineItem" table="CNTRCT.LINE_ITEM">
            <field name="Contract.id" column="ID"/>
            ...
        </class>
        ...
    </package>
</orm>

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.

<?xml version="1.0"?>
<orm>
    <package name="org.mag.pub">
        <class name="Company" table="COMP">
            <field name="address">
                <embedded null-indicator-column="STREET">
                    <field name="street" column="STREET"/>
                    <field name="city" column="CITY"/>
                    <field name="state">
                        <column name="STATE" jdbc-type="char" length="2"/>
                    </field>
                    <field name="zip" column="ZIP"/>
                </embedded>
            </field>
            ...
        </class>    
        <class name="Author" table="AUTH">
            <field name="address">
                <embedded null-indicator-column="STREET">
                    <field name="street" column="STREET"/>
                    <field name="city" column="CITY"/>
                    <field name="state">
                        <column name="STATE" jdbc-type="char" length="2"/>
                    </field>
                    <field name="zip" column="ZIP"/>
                </embedded>
            </field>
            ...
        </class>    
        ...
    </package>
</orm>

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:

Then the mapping would be:

Example 15.29. Embedded Collection Elements

<class name="Company" table="COMP">
    <datastore-identity column="CID" strategy="autoassign"/>
    <field name="addresses" table="COMP_ADDRS">
        <join column="COMP_ID"/> 
        <element>
            <embedded>
                <field name="street" column="STREET"/>
                <field name="city" column="CITY"/>
                <field name="state">
                    <column name="STATE" jdbc-type="char" length="2"/>
                </field>
                <field name="zip" column="ZIP"/>
            </embedded>
        </element>
    </field>
    ...
</class>

Normally, you will not use a null indicator column for embedded collection elements, map keys, and map values. Only do so if you plan on storing null values in your collection or map.

 

Skip navigation bar   Back to Top