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.
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
kodo.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”.
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.VersionColumn
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.VersionColumns
annotation to declare an array of
VersionColumn
values.
Each VersionColumn
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”.
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
kodo.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”.
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
kodo.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
kodo.persistence.jdbc.XJoinColumns
annotation. The value of this annotation is an array of
individual XJoinColumn
s.
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”.
AttributeOverride
s 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.EmbeddedMapping
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.
MappingOverride[] overrides
: This array
allows you to override any mapping of the embedded object.
The EmbeddedMapping
's
overrides
array serves the same purpose as
standard JPA's AttributeOverride
s and
AssociationOverride
s. In fact,
you can also use the MappingOverride
annotation on an entity class to override a complex mapping of its
mapped superclass, just as you can with
AttributeOverride
and
AssociationOverride
s. The MappingOverrides
annotation, whose value is an array of
MappingOverride
s, allows you to overide
multiple mapped superclass mappings.
Each
kodo.persistence.jdbc.MappingOverride
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.*; @Embeddable public class PathCoordinate { private String siteName; @Persistent @Strategy("com.xyz.kodo.PointValueHandler") private Point point; ... } @Entity public class Path { @Embedded @EmbeddedMapping(nullIndicatorFieldName="siteName", overrides={ @MappingOverride(name="siteName", columns=@Column(name="START_SITE")), @MappingOverride(name="point", columns={ @Column(name="START_X"), @Column(name="START_Y") }) }) private PathCoordinate start; ... }
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
ElementColumn
s,
ElementJoinColumn
s, or an
ElementEmbeddedMapping
. We explore the annotations
below.
The
kodo.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.
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
ElementColumn
s.
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.
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
kodo.persistence.jdbc.ElementJoinColumn
annotation. To declare a compound join, enclose an
array of ElementJoinColumn
s in the
kodo.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
XJoinColumn
s above,
ElementJoinColumn
s 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.
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.
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
kodo.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”.
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.*; import kodo.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.*; import kodo.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=@MappingOverride(name="state", columns=@Column(columnDefinition="CHAR(2)"))) private Collection<Address> addresses; ... }
The previous section covered the use of ElementJoinColumn
annotations in conjunction with a
ContainerTable
for mapping collections to dedicate
tables. ElementJoinColumn
s, 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 LineItem
s, 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 kodo.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; ... }
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 KeyColumn
s,
KeyJoinColumn
s, or an
KeyEmbeddedMapping
and
ElementColumn
s,
ElementJoinColumn
s, 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.
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
KeyColumn
s.
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.
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 KeyJoinColumn
s 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
XJoinColumn
s above,
KeyJoinColumn
s 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.
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.
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.*; import kodo.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; ... }
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.
The
kodo.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
kodo.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.
The
kodo.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
kodo.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
kodo.persistence.jdbc.ForeignKeyAction
enum identifying the desired
delete action. Defaults to RESTRICT
.
ForeignKeyAction updateAction
:
Value from the
kodo.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.
The
kodo.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.
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.*; import kodo.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; ... }
![]() ![]() |