7.7. Additional JPA Mappings

7.7.1. Datastore Identity Mapping
7.7.2. Surrogate Version Mapping
7.7.3. Multi-Column Mappings
7.7.4. Join Column Attribute Targets
7.7.5. Embedded Mapping
7.7.6. Collections
7.7.6.1. Container Table
7.7.6.2. Element Columns
7.7.6.3. Element Join Columns
7.7.6.4. Element Embedded Mapping
7.7.6.5. Order Column
7.7.6.6. Examples
7.7.7. One-Sided One-Many Mapping
7.7.8. Maps
7.7.8.1. Key Columns
7.7.8.2. Key Join Columns
7.7.8.3. Key Embedded Mapping
7.7.8.4. Examples
7.7.9. Indexes and Constraints
7.7.9.1. Indexes
7.7.9.2. Foreign Keys
7.7.9.3. Unique Constraints
7.7.9.4. Examples

Kodo supports many persistence strategies beyond those of the JPA specification. Section 6.3, “Additional JPA Metadata” covered the logical metadata for Kodo's additional persistence strategies. We now demonstrate how to map entities using these strategies to the database.

7.7.1. Datastore Identity Mapping

Section 5.3, “Object Identity” describes how to use datastore identity in JPA. Kodo requires a single numeric primary key column to hold datastore identity values. The org.apache.openjpa.persistence.jdbc.DataStoreIdColumn annotation customizes the datastore identity column. This annotation has the following properties:

  • String name: Defaults to ID.

  • int precision

  • String columnDefinition

  • boolean insertable

  • boolean updatable

All properties correspond exactly to the same-named properties on the standard Column annotation, described in Section 12.3, “Column”.

Example 7.27. Datastore Identity Mapping

import org.apache.openjpa.persistence.*;
import org.apache.openjpa.persistence.jdbc.*;

@Entity
@Table(name="LOGS")
@DataStoreIdColumn(name="ENTRY")
public class LogEntry
{
    @Lob
    private String content;

    ...
}

7.7.2. Surrogate Version Mapping

Kodo supports version fields as defined by the JPA specification, but allows you to use a surrogate version column in place of a version field if you like. You map the surrogate version column with the kodo.persistence.jdbc.LockGroupVersionColumn annotation. If you take advantage of Kodo's ability to define multiple lock groups, you may have multiple version columns. In that case, use the kodo.persistence.jdbc.LockGroupVersionColumns annotation to declare an array of LockGroupVersionColumn values. Each LockGroupVersionColumn has the following properties:

  • String name: Defaults to VERSN.

  • String lockGroup

  • int length

  • int precision

  • int scale

  • String columnDefinition

  • boolean nullable

  • boolean insertable

  • boolean updatable

The lockGroup property allows you to specify that a version column is for some lock group other than the default group. See Section 5.8, “Lock Groups” for an example. All other properties correspond exactly to the same-named properties on the standard Column annotation, described in Section 12.3, “Column”.

By default, Kodo assumes that surrogate versioning uses a version number strategy. You can choose a different strategy with the VersionStrategy annotation described in Section 7.9.1.4, “Version Strategy”.

7.7.3. Multi-Column Mappings

Kodo makes it easy to create multi-column custom mappings. The JPA specification includes a Column annotation, but is missing a way to declare multiple columns for a single field. Kodo remedies this with the org.apache.openjpa.persistence.jdbc.Columns annotation, which contains an array of Column values. Example 7.34, “Custom Mappings via Extensions” uses Kodo's Columns annotation to map a java.awt.Point to separate X and Y columns.

Remember to annotate custom field types with Persistent, as described in Section 6.3.3, “Persistent Field Values”.

7.7.4. Join Column Attribute Targets

Section 12.8.4, “Direct Relations” in the JPA Overview introduced you to the JoinColumn annotation. A JoinColumn's referencedColumnName property declares which column in the table of the related type this join column links to. Suppose, however, that the related type is unmapped, or that it is part of a table-per-class inheritance hierarchy. Each subclass that might be assigned to the field could reside in a different table, and could use entirely different names for its primary key columns. It becomes impossible to supply a single referencedColumnName that works for all subclasses.

Kodo rectifies this by allowing you to declare which attribute in the related type each join column links to, rather than which column. If the attribute is mapped differently in various subclass tables, Kodo automatically forms the proper join for the subclass record at hand. The org.apache.openjpa.persistence.jdbc.XJoinColumn annotation has all the same properties as the standard JoinColumn annotation, but adds an additional referencedAttributeName property for this purpose. Simply use a XJoinColumn in place of a JoinColumn whenever you need to access this added functionality.

For compound keys, use the org.apache.openjpa.persistence.jdbc.XJoinColumns annotation. The value of this annotation is an array of individual XJoinColumns.

7.7.5. Embedded Mapping

JPA uses the AttributeOverride annotation to override the default mappings of an embeddable class. The JPA Overview details this process in Section 12.8.3, “Embedded Mapping”. AttributeOverrides suffice for simple mappings, but do not allow you to override complex mappings. Also, JPA has no way to differentitate between a null embedded object and one with default values for all of its fields.

Kodo overcomes these shortcomings with the kodo.persistence.jdbc.XEmbeddedMapping annotation. This annotation has the following properties:

  • String nullIndicatorColumnName: If the named column's value is NULL, then the embedded object is assumed to be null. If the named column has a non-NULL value, then the embedded object will get loaded and populated with data from the other embedded fields. This property is entirely optional. By default, Kodo always assumes the embedded object is non-null, just as in standard JPA mapping.

    If the column you name does not belong to any fields of the embedded object, Kodo will create a synthetic null-indicator column with this name. In fact, you can specify a value of true to simply indicate that you want a synthetic null-indicator column, without having to come up with a name for it. A value of false signals that you explicitly do not want a null-indicator column created for this mapping (in case you have configured your mapping defaults to create one by default).

  • String nullIndicatorFieldName: Rather than name a null indicator column, you can name a field of the embedded type. Kodo will use the column of this field as the null-indicator column.

  • XMappingOverride[] overrides: This array allows you to override any mapping of the embedded object.

The XEmbeddedMapping's overrides array serves the same purpose as standard JPA's AttributeOverrides and AssociationOverrides. In fact, you can also use the XMappingOverride annotation on an entity class to override a complex mapping of its mapped superclass, just as you can with AttributeOverride and AssociationOverrides. The XMappingOverrides annotation, whose value is an array of XMappingOverrides, allows you to overide multiple mapped superclass mappings.

Each kodo.persistence.jdbc.XMappingOverride annotation has the following properties:

  • String name: The name of the field that is being overridden.

  • Column[] columns: Columns for the new field mapping.

  • XJoinColumn[] joinColumns: Join columns for the new field mapping, if it is a relation field.

  • ContainerTable containerTable: Table for the new collection or map field mapping. We cover collection mappings in Section 7.7.6, “Collections”, and map mappings in Section 7.7.8, “Maps”.

  • ElementColumn[] elementColumns: Element columns for the new collection or map field mapping. You will see how to use element columns in Section 7.7.6.2, “Element Columns”.

  • ElementJoinColumn[] elementJoinColumns: Element join columns for the new collection or map field mapping. You will see how to use element join columns in Section 7.7.6.3, “Element Join Columns”.

  • KeyColumn[] keyColumns: Map key columns for the new map field mapping. You will see how to use key columns in Section 7.7.8.1, “Key Columns”.

  • KeyJoinColumn[] keyJoinColumns: Key join columns for the new map field mapping. You will see how to use key join columns in Section 7.7.8.2, “Key Join Columns”.

The following example defines an embeddable PathCoordinate class with a custom mapping of a java.awt.Point field to two columns. It then defines an entity which embeds a PointCoordinate and overrides the default mapping for the point field. The entity also declares that if the PathCoordinate's siteName field column is null, it means that no PathCoordinate is stored in the embedded record; the owning field will load as null.

Example 7.28. Overriding Complex Mappings

import kodo.persistence.jdbc.*;
import org.apache.openjpa.jdbc.persistence.jdbc.*;

@Embeddable
public class PathCoordinate
{
    private String siteName;

    @Persistent
    @Strategy("com.xyz.kodo.PointValueHandler")
    private Point point;

    ...
}

@Entity
public class Path
{
    @Embedded
    @XEmbeddedMapping(nullIndicatorFieldName="siteName", overrides={
        @XMappingOverride(name="siteName", columns=@Column(name="START_SITE")),
        @XMappingOverride(name="point", columns={
            @Column(name="START_X"),
            @Column(name="START_Y")
        })
    })
    private PathCoordinate start;

    ...
}

7.7.6. Collections

In Section 6.3.4, “Persistent Collection Fields”, we explored the PersistentCollection annotation for persistent collection fields that aren't a standard OneToMany or ManyToMany relation. To map these non-standard collections, combine Kodo's ContainerTable annotation with ElementColumns, ElementJoinColumns, or an ElementEmbeddedMapping. We explore the annotations below.

7.7.6.1. Container Table

The org.apache.openjpa.persistence.jdbc.ContainerTable annotation describes a database table that holds collection (or map) elements. This annotation has the following properties:

  • String name

  • String catalog

  • String schema

  • XJoinColumn[] joinColumns

  • ForeignKey joinForeignKey

  • Index joinIndex

The name, catalog, schema, and joinColumns properties describe the container table and how it joins to the owning entity's table. These properties correspond to the same-named properties on the standard JoinTable annotation, described in Section 12.8.5, “Join Table”. If left unspecified, the name of the table defaults to the first five characters of the entity table name, plus an underscore, plus the field name. The joinForeignKey and joinIndex properties override default foreign key and index generation for the join columns. We explore foreign keys and indexes later in this chapter.

You may notice that the container table does not define how to store the collection elements. That is left to separate annotations, which are the subject of the next sections.

7.7.6.2. Element Columns

Just as the JPA Column annotation maps a simple value (primitive wrapper, String, etc), Kodo's kodo.persistence.jdbc.ElementColumn annotation maps a simple element value. To map custom multi-column elements, use the kodo.persistence.jdbc.ElementColumns annotation, whose value is an array of ElementColumns.

An ElementColumn always resides in a container table, so it does not have the table property of a standard Column. Otherwise, the ElementColumn and standard Column annotations are equivalent. See Section 12.3, “Column” in the JPA Overview for a review of the Column annotation.

7.7.6.3. Element Join Columns

Element join columns are equivalent to standard JPA join columns, except that they represent a join to a collection or map element entity rather than a direct relation. You represent an element join column with Kodo's org.apache.openjpa.persistence.jdbc.ElementJoinColumn annotation. To declare a compound join, enclose an array of ElementJoinColumns in the org.apache.openjpa.persistence.jdbc.ElementJoinColumns annotation.

An ElementJoinColumn always resides in a container table, so it does not have the table property of a standard JoinColumn. Like XJoinColumns above, ElementJoinColumns can reference a linked attribute rather than a static linked column. Otherwise, the ElementJoinColumn and standard JoinColumn annotations are equivalent. See Section 12.8.4, “Direct Relations” in the JPA Overview for a review of the JoinColumn annotation.

7.7.6.4. Element Embedded Mapping

The kodo.persistence.jdbc.ElementEmbeddedMapping annotation allows you to map your collection or map's embedded element type to your container table. This annotation has exactly the same properties as the EmbeddedMapping annotation described above.

7.7.6.5. Order Column

Relational databases do not guarantee that records are returned in insertion order. If you want to make sure that your collection elements are loaded in the same order they were in when last stored, you must declare an order column. Kodo's org.apache.openjpa.persistence.jdbc.OrderColumn annotation has the following properties:

  • String name: Defaults to ORDR.

  • boolean enabled

  • int precision

  • String columnDefinition

  • boolean insertable

  • boolean updatable

Order columns are always in the container table. You can explicitly turn off ordering (if you have enabled it by default via your mapping defaults) by setting the enabled property to false. All other properties correspond exactly to the same-named properties on the standard Column annotation, described in Section 12.3, “Column”.

7.7.6.6. Examples

Our first example maps the Article.subtitles field to the ART_SUBS container table, as shown in the diagram above. Notice the use of ContainerTable in combination with ElementColumn and OrderColumn to map this ordered list of strings.

Example 7.29. String List Mapping

package org.mag;

import kodo.persistence.jdbc.*;
import org.apache.openjpa.persistence.*;
import org.apache.openjpa.persistence.jdbc.*;

@Entity
@Table(name="ART")
public class Article
{
    @Id private long id;

    @PersistentCollection
    @ContainerTable(name="ART_SUBS", joinColumns=@XJoinColumn(name="ART_ID"))
    @ElementColumn(name="SUBTITLE")
    @OrderColumn(name="ORD")
    private List<String> subtitles;

    ...
}

Now we map a collection of embedded Address objects for a Company, according to the following diagram:

Example 7.30. Embedded Element Mapping

package org.mag.pub;

import kodo.persistence.jdbc.*;
import org.apache.openjpa.persistence.*;
import org.apache.openjpa.persistence.jdbc.*;

@Embeddable
public class Address
{
    ...
}

@Entity
@Table(name="COMP")
public class Company
{
    @Id private long id;
  
    @PersistentCollection(elementEmbedded=true)
    @ContainerTable(name="COMP_ADDRS", joinColumns=@XJoinColumn(name="COMP_ID"))
    @ElementEmbeddedMapping(overrides=@XMappingOverride(name="state", 
        columns=@Column(columnDefinition="CHAR(2)")))
    private Collection<Address> addresses;

    ...
}

7.7.7. One-Sided One-Many Mapping

The previous section covered the use of ElementJoinColumn annotations in conjunction with a ContainerTable for mapping collections to dedicate tables. ElementJoinColumns, however, have one additional use: to create a one-sided one-many mapping. Standard JPA supports OneToMany fields without a mappedBy inverse, but only by mapping these fields to a JoinTable (see Section 12.8.5, “Join Table” in the JPA Overview for details). Often, you'd like to create a one-many association based on an inverse foreign key (logical or actual) in the table of the related type.

Consider the model above. Subscription has a collection of LineItems, but LineItem has no inverse relation to Subscription. To retrieve all of the LineItem records for a Subscription, we join the SUB_ID inverse foreign key column in the LINE_ITEM table to the primary key column of the SUB table. The example below shows how to represent this model in mapping annotations. Note that Kodo automatically assumes an inverse foreign key mapping when element join columns are given, but no container or join table is given.

Example 7.31. One-Sided One-Many Mapping

package org.mag.subscribe;

import org.apache.openjpa.persistence.jdbc.*;

@Entity
@Table(name="LINE_ITEM", schema="CNTRCT")
public class LineItem
{
    ...
}

@Entity
@Table(name="SUB", schema="CNTRCT")
public class Subscription
{
    @Id private long id;

    @OneToMany
    @ElementJoinColumn(name="SUB_ID", target="ID")
    private Collection<LineItem> items;

    ...
}

7.7.8. Maps

Section 6.3.5, “Persistent Map Fields” discussed the PersistentMap annotation for persistent map fields. To map these non-standard fields to the database, combine Kodo's ContainerTable annotation with KeyColumns, KeyJoinColumns, or an KeyEmbeddedMapping and ElementColumns, ElementJoinColumns, or an ElementEmbeddedMapping.

We detailed the ContainerTable annotation in Section 7.7.6.1, “Container Table”. We also discussed element columns, join columns, and embedded mappings in Section 7.7.6.2, “Element Columns”, Section 7.7.6.3, “Element Join Columns”, and Section 7.7.6.4, “Element Embedded Mapping”. Key columns, join columns, and embedded mappings are new, however; we tackle them below.

7.7.8.1. Key Columns

Key columns serve the same role for map keys as the element columns described in Section 7.7.6.2, “Element Columns” serve for collection elements. Kodo's kodo.persistence.jdbc.KeyColumn annotation represents a map key. To map custom multi-column keys, use the kodo.persistence.jdbc.KeyColumns annotation, whose value is an array of KeyColumns.

A KeyColumn always resides in a container table, so it does not have the table property of a standard Column. Otherwise, the KeyColumn and standard Column annotations are equivalent. See Section 12.3, “Column” in the JPA Overview for a review of the Column annotation.

7.7.8.2. Key Join Columns

Key join columns are equivalent to standard JPA join columns, except that they represent a join to a map key entity rather than a direct relation. You represent a key join column with Kodo's kodo.persistence.jdbc.KeyJoinColumn annotation. To declare a compound join, enclose an array of KeyJoinColumns in the kodo.persistence.jdbc.KeyJoinColumns annotation.

A KeyJoinColumn always resides in a container table, so it does not have the table property of a standard JoinColumn. Like XJoinColumns above, KeyJoinColumns can reference a linked field rather than a static linked column. Otherwise, the KeyJoinColumn and standard JoinColumn annotations are equivalent. See Section 12.8.4, “Direct Relations” in the JPA Overview for a review of the JoinColumn annotation.

7.7.8.3. Key Embedded Mapping

The kodo.persistence.jdbc.KeyEmbeddedMapping annotation allows you to map your map field's embedded key type to your container table. This annotation has exactly the same properties as the EmbeddedMapping annotation described above.

7.7.8.4. Examples

Map mapping in Kodo uses the same principles you saw in collection mapping. The example below maps the Article.authors map according to the diagram above.

Example 7.32. String Key, Entity Value Map Mapping

package org.mag.pub;

import kodo.persistence.jdbc.*;
import org.apache.openjpa.persistence.*;
import org.apache.openjpa.persistence.jdbc.*;

@Entity
@Table(name="AUTH")
@DataStoreIdColumn(name="AID" columnDefinition="INTEGER64")
public class Author
{
    ...
}

package org.mag;

@Entity
@Table(name="ART")
public class Article
{
    @Id private long id;
 
    @PersistentMap
    @ContainerTable(name="ART_AUTHS", joinColumns=@XJoinColumn(name="ART_ID"))
    @KeyColumn(name="LNAME")
    @ElementJoinColumn(name="AUTH_ID")
    private Map<String,Author> authors;

    ...
}

7.7.9. Indexes and Constraints

Kodo uses index information during schema generation to index the proper columns. Kodo uses foreign key and unique constraint information during schema creation to generate the proper database constraints, and also at runtime to order SQL statements to avoid constraint violations while maximizing SQL batch size.

Kodo assumes certain columns have indexes or constraints based on your mapping defaults, as detailed in Section 7.4, “Mapping Defaults”. You can override the configured defaults on individual joins, field values, collection elements, map keys, or map values using the annotations presented in the following sections.

7.7.9.1. Indexes

The org.apache.openjpa.persistence.jdbc.Index annotation represents an index on the columns of a field. It is also used within the ConterainTable annotation to index join columns.

To index the columns of a collection or map element or map key, use the org.apache.openjpa.persistence.jdbc.ElementIndex and kodo.persistence.jdbc.KeyIndex annotations, respectively. These annotations have the following properties:

  • boolean enabled: Set this property to false to explicitly tell Kodo not to index these columns, when Kodo would otherwise do so.

  • String name: The name of the index. Kodo will choose a name if you do not provide one.

  • boolean unique: Whether to create a unique index. Defaults to false.

7.7.9.2. Foreign Keys

The org.apache.openjpa.persistence.jdbc.ForeignKey annotation represents a foreign key on the columns of a field. It is also used within the ContainerTable annotation to set a database foreign key on join columns.

To set a constraint to the columns of a collection or map element or map value, use the org.apache.openjpa.persistence.jdbc.ElementForeignKey and kodo.persistence.jdbc.KeyForeignKey annotations, respectively. These annotations have the following properties:

  • boolean enabled: Set this property to false to explicitly tell Kodo not to set a foreign key on these columns, when Kodo would otherwise do so.

  • String name: The name of the foreign key. Kodo will choose a name if you do not provide one, or will create an anonymous key.

  • boolean deferred: Whether to create a deferred key if supported by the database.

  • ForeignKeyAction deleteAction: Value from the org.apache.openjpa.persistence.jdbc.ForeignKeyAction enum identifying the desired delete action. Defaults to RESTRICT.

  • ForeignKeyAction updateAction: Value from the org.apache.openjpa.persistence.jdbc.ForeignKeyAction enum identifying the desired update action. Defaults to RESTRICT.

Keep in mind that Kodo uses foreign key information at runtime to avoid constraint violations; it is important, therefore, that your mapping defaults and foreign key annotations combine to accurately reflect your existing database constraints, or that you configure Kodo to reflect on your database schema to discover existing foreign keys (see Section 4.13.2, “Schema Factory”).

7.7.9.3. Unique Constraints

The org.apache.openjpa.persistence.jdbc.Unique annotation represents a unqiue constraint on the columns of a field. It is more convenient than using the uniqueConstraints property of standard JPA Table and SecondaryTable annotations, because you can apply it directly to the constrained field. The Unique annotation has the following properties:

  • boolean enabled: Set this property to false to explicitly tell Kodo not to constrain these columns, when Kodo would otherwise do so.

  • String name: The name of the constraint. Kodo will choose a name if you do not provide one, or will create an anonymous constraint.

  • boolean deferred: Whether to create a deferred constraint if supported by the database.

7.7.9.4. Examples

Here again is our map example from Section 7.7.8, “Maps”, now with explicit indexes and constraints added.

Example 7.33. Constraint Mapping

package org.mag.pub;

import kodo.persistence.jdbc.*;
import org.apache.openjpa.persistence.*;
import org.apache.openjpa.persistence.jdbc.*;

@Entity
@Table(name="AUTH")
@DataStoreIdColumn(name="AID" columnDefinition="INTEGER64")
public class Author
{
    ...
}

package org.mag;

@Entity
@Table(name="ART")
public class Article
{
    @Id private long id;
 
    @PersistentMap
    @ContainerTable(name="ART_AUTHS", joinColumns=@XJoinColumn(name="ART_ID")
        joinForeignKey=@ForeignKey(deleteAction=ForeignKeyAction.CASCADE))
    @KeyColumn(name="LNAME")
    @KeyIndex(name="I_AUTH_LNAME")
    @ElementJoinColumn(name="AUTH_ID")
    @ElementForeignKey(deleteAction=ForeignKeyAction.RESTRICT)
    private Map<String,Author> authors;

    ...
}

 

Skip navigation bar   Back to Top