In the 1990's programmers coined the term impedance mismatch to describe the difficulties in bridging the object and relational worlds. Perhaps no feature of object modeling highlights the impedance mismatch better than inheritance. There is no natural, efficient way to represent an inheritance relationship in a relational database.
Luckily, JDOR provides very flexible object-relational mapping of
inheritance trees, making the best of a bad situation. Each class in
an inheritance hierarchy can use the inheritance
element to describe its inheritance mapping. This element has a
single attribute: strategy
. In the list that
follows, we examine JDOR's standard inheritance strategies. Some
vendors may define additional proprietary strategies.
Note | |
---|---|
Kodo does not define any non-standard inheritance models, but does allow you to create your own custom mappings. See the Reference Guide's Section 7.10, “Custom Mappings” for details on writing custom inheritance strategies and pluggin them in to Kodo. |
The subclass-table
inheritance strategy indicates
that the current class is not mapped to any table at all, and that
all of its fields must be mapped by subclasses into their own
tables. This strategy is typically used with abstract base classes
that are not represented in the relational schema.
Classes that declare an inheritance strategy of
subclass-table
should not define the
class
element's table
attribute, nor should they attempt to define mappings for any
fields.
In our model, the abstract Contract
class
uses the subclass-table
inheritance strategy.
Contract
's two direct subclasses,
Subscription
and
LineItem
, each map all of
Contract
's fields into their own tables.
Example 15.4. subclass-table Mapping
<class name="Contract"> <inheritance strategy="subclass-table"/> ... </class>
Note | |
---|---|
The pattern of mapping the fields of an abstract, unmapped base class into the tables of each subclass is often referred to as a horizontal or distributed inheritance mapping. |
An advantage of using the subclass-table
strategy for abstract base classes is that properties common
to multiple persistent subclasses can be defined in the
superclass without having to suffer the performance
consequences and relational design restrictions inherent in
other strategies (which we will examine shortly). Persisting
and modifying instances of subclasses is efficient, typically
only requiring a single INSERT
or
UPDATE
statement. Loading
relations to these subclasses is also efficient.
Though relations to mapped subclasses of a
subclass-table
class are very efficient,
relations to the unmapped base class itself are equally
inefficient. When the concrete subclass
is not known, the related object could be in any of the
subclass tables, making joins through the relation
impossible. Subclasses can even use repeated primary key
values, forcing the JDOR implementation to record more than just
the related primary key values in the database.
This ambiguity also affects queries and identity lookups:
queries against a subclass-table
base class
require either multiple SELECT
statements
(one for each mapped subclass), or a complex SQL
UNION
.
Note | |
---|---|
Kodo provides metadata extensions you can use to indicate
that a field declared as a relation to a base class is
actually a relation to a specific subclass. These
extensions often alleviate the need for mapping a relation
to a
When a relation to a |
Here are some additional caveats to consider when using
the subclass-table
inheritance mapping:
Declaring classes abstract.
You are not required to make all subclass-table
classes abstract. However, we recommend
that you do so, as any attempt to persist a
subclass-table
base class instance
will result in an exception on flush.
Application identity.
When a subclass-table
superclass
uses application identity, you must observe one of the
following two restrictions:
The primary key values in each of the tables
that extend the subclass-table
superclass must be unique. In our
model, that means that there can be no row in
CNTRCT.SUB
with the same
primary key value as a row in
CNTRCT.LINE_ITEM
.
This is because a call to
getObjectById
with an
application identity instance cannot identify
which subclass the ID is associated with.
Rather than having a single application identity class associated with the entire class hierarchy, each subclass declares its own application identity class. The inheritance hierarchy of the application identity classes must exactly match the inheritance hierarchy of the persistent classes they represent, as discussed in Section 4.5.2.1, “Application Identity Hierarchies”.
The new-table
inheritance strategy employs a
new table to hold the fields of the class. You must specify the
table name in the class
element's
table
attribute.
new-table
is the default strategy for base
persistent classes and for subclasses of subclass-table
classes. Classes that extend non-subclass-table
base classes can also use this strategy to map their
fields to a new table, rather than to the superclass table. The
subclass table might contain only subclass state, or might re-map
all superclass state as well.
When the subclass table only contains subclass state, the JDOR
implementation must be able to link corresponding records in
the superclass and subclass tables together to retrieve all of
the persistent state for a subclass
instance. You tell the JDOR implementation how to
do this by nesting a join
element in your
inheritance
element. Thus, this is often
referred to as a joined inheritance
strategy.
As its name implies, the join
element maps a
logical foreign key from the subclass table to the superclass
table. It has a column
attribute and nested
column
elements for representing the
subclass table's foreign key columns.
Section 15.7, “Joins” demonstrated how to
use column
s to map joins. Typically, the
subclass table's foreign key columns are also that table's
primary key columns.
All of the classes in our model except Contract
and LifetimeSubscription
(not pictured) use the new-table
strategy.
Most of these classes are either base classes or direct
subclasses of Contract
, a
subclass-table
class. In these cases, the
new-table
strategy is the default.
TrialSubscription
, however, extends
Subscription
, which is itself mapped to
a table. Therefore, TrialSubscription
could have mapped its fields to
Subscription
's table.
Instead, TrialSubscription
maps its
declared fields to the CNTRCT.TRIAL_SUB
table, and joins to the CNTRCT.SUB
table for
base class state. The join is made by linking the
CNTRCT.TRIAL_SUB.ID
foreign key column to the
CNTRCT.SUB.ID
primary key column. The
example below shows how to represent this in mapping metadata.
Example 15.5. Joined Subclass Tables
<class name="TrialSubscription" table="CNTRCT.TRIAL_SUB"> <inheritance strategy="new-table"> <join> <column name="ID" target="CNTRCT.SUB.ID"/> </join> </inheritance> ... </class>
That is the long version, however. JDOR is smart enough to default the target table to the superclass table, and we can apply join shortcuts to yield a much more concise representation:
<class name="TrialSubscription" table="CNTRCT.TRIAL_SUB"> <inheritance strategy="new-table"> <join column="ID"/> </inheritance> ... </class>
If you want to map a class to a table and none of the
superclasses of that class are themselves mapped to a table
(or the class has no persistent superclasses), then the
new-table
strategy is your only choice.
It is only meaningful to discuss the advantages and
disadvantages of the new-table
strategy
for classes that have a superclass mapped to a different
table. For example, our model's
TrialSubscription
class extends
Subscription
, yet maps its declared fields to a
different table. We could have mapped
TrialSubscription
's fields to
Subscription
's table; what made us
choose to to use a separate joined table instead?
Using joined subclass tables results in the most normalized database schema, meaning the schema with the least spurious or redundant data.
As more subclasses are added to the data model over time, the only schema modification that needs to be made is the addition of corresponding subclass tables in the database, rather than having to change the structure of existing tables.
Relations to a base class using the
new-table
strategy can be loaded
through standard joins and can use standard foreign
keys, as opposed to the machinations required to
load relations to subclass-table
base types.
Using multiple joined tables slows things down.
Retrieving any subclass requires one or more database joins,
and storing subclasses requires multiple INSERT
or UPDATE
statements. This
is only the case when persistence operations are performed
on subclasses; if most operations are performed on the
least-derived persistent superclass, then this mapping is
very fast.
Note | |
---|---|
When executing a select against a hierarchy that uses joined subclass table inheritance, you must consider how to load subclass state. Section 5.7, “Eager Fetching” in the Reference Guide describes Kodo's options for efficient data loading. |
Like the joined new-table
strategy, the
table-per-class new-table
strategy maps a subclass to its own table.
Unlike the joined strategy, however, the subclass table
includes all state for an instance of the corresponding class.
Thus to load a subclass instance, the JDOR implementation must
only read from the subclass table; it does not need to join to
superclass tables.
Suppose that our sample model's Magazine
class has a subclass Tabloid
. The
classes are mapped using the table-per-class strategy, as in
the diagram above. In a table-per-class mapping,
Magazine
's table MAG
contains
all state declared in the base Magazine
class. Tabloid
maps to a separate table,
TABLOID
. This table contains not only the
state declared in the Tabloid
subclass,
but all the base class state from Magazine
as well. Thus the TABLOID
table would contain columns for isbn
,
title
, and other Magazine
fields.
Example 15.6. Table Per Class Mapping
<class name="Tabloid" table="TABLOID"> <inheritance strategy="new-table"/> <field name="Magazine.isbn" column="ISBN"/> <field name="Magazine.title" column="TITLE"/> ... <field name="data" column="TAB_DATA"/> </class>
Notice that only the lack of a join
within the inheritance
element differentiates
a table-per-class strategy from a joined strategy. Also, notice
that a table-per-class subclass explicitly re-maps all of its
inherited fields into its own table.
The table-per-class strategy is very efficient when operating on instances of a known class. Under these conditions, the strategy never requires joining to superclass or subclass tables. Reads, joins, inserts, updates, and deletes are all efficient in the absence of polymorphic behavior. Also, as in the joined strategy, adding additional classes to the hierarchy does not require modifying existing class tables.
Polymorphic relations to a non-leaf classes in a
table-per-class hierarchy have many limitations.
In some ways, they are similar to relations to a
subclass-table
base class. When
the concrete subclass is not known, the related object
could be in any of the subclass tables, making joins
through the relation impossible. This ambiguity also
affects identity lookups and queries; these
operations require multiple SQL SELECT
s
(one for each possible subclass), or a complex
UNION
.
Note | |
---|---|
Section 7.8.1, “Table Per Class” in the Reference Guide describes the limitations Kodo places on table-per-class mapping. |
superclass-table
is the default strategy for
subclasses of new-table
and other
superclass-table
classes. In this strategy,
the subclass' fields are mapped to superclass' table. Classes
that use the superclass-table
inheritance
strategy should not specify the class
element's
table
attribute.
In our model, Subscription
is mapped to the
CNTRCT.SUB
table.
LifetimeSubscription
, which extends
Subscription
, adds its field data to this table as well.
Example 15.7. superclass-table Mapping
<class name="LifetimeSubscription"> <inheritance strategy="superclass-table"/> ... </class>
superclass-table
inheritance mapping is the
fastest of all inheritance models, since it never requires a
join to retrieve a persistent instance from the database.
Similarly, persisting or updating a persistent instance
requires only a single INSERT
or
UPDATE
statement. Finally, relations to
any class within a superclass-table
inheritance hierarchy are just as efficient as relations to
a base class.
The larger the inheritance model gets, the "wider" the mapped table gets, in that for every field in the entire inheritance hierarchy, a column must exist in the mapped table. This may have undesirable consequence on the database size, since a wide or deep inheritance hierarchy will result in tables with many mostly-empty columns.
Now that we have covered JDOR's inheritance strategies, we can update our mapping document with inheritance information. Here is the complete model:
And here is the corresponding mapping metadata:
Example 15.8. Inheritance Mapping
<?xml version="1.0"?> <orm> <package name="org.mag"> <sequence name="ArticleSeq" datastore-sequence="ART_SEQ"/> <class name="Magazine" table="MAG"> <!-- not strictly necessary, since this is the default --> <inheritance strategy="new-table"/> <field name="isbn"> <column name="ISBN" jdbc-type="char" length="15"/> </field> <field name="title" column="TITLE"/> ... </class> <class name="Article" table="ART"> <!-- not strictly necessary, since this is the default --> <inheritance strategy="new-table"/> <field name="id" column="ID"/> ... </class> </package> <package name="org.mag.pub"> <sequence name="AuthorSeq" factory-class="Author$SequenceFactory"/> <class name="Company" table="COMP"> <datastore-identity column="CID" strategy="autoassign"/> <!-- not strictly necessary, since this is the default --> <inheritance strategy="new-table"/> ... </class> <class name="Author" table="AUTH"> <datastore-identity sequence="AuthorSeq"> <column name="AID" sql-type="INTEGER64"/> </datastore-identity> <!-- not strictly necessary, since this is the default --> <inheritance strategy="new-table"/> ... </class> </package> <package name="org.mag.subscribe"> <sequence name="ContractSeq" strategy="transactional"/> <class name="Contract"> <inheritance strategy="subclass-table"/> ... </class> <class name="Subscription" table="CNTRCT.SUB"> <!-- not strictly necessary, since this is the default --> <inheritance strategy="new-table"/> <field name="Contract.id" column="ID"/> ... </class> <class name="LifetimeSubscription"> <!-- not strictly necessary, since this is the default --> <inheritance strategy="superclass-table"/> ... </class> <class name="TrialSubscription" table="CNTRCT.TRIAL_SUB"> <inheritance strategy="new-table"> <join column="ID"/> </inheritance> ... </class> <class name="Subscription$LineItem" table="CNTRCT.LINE_ITEM"> <!-- not strictly necessary, since this is the default --> <inheritance strategy="new-table"/> <field name="Contract.id" column="ID"/> ... </class> </package> </orm>