JDO requires that implementations support the persistence of interfaces as first class objects
(FCO's). DataNucleus provides this capability. It follows the same general process as for
java.lang.Object since both interfaces and java.lang.Object are
basically
references
to some persistable object.
To demonstrate interface handling lets introduce some classes.
Let's suppose you have an interface with a selection of classes implementing the interface
something like this
You then have a class that contains an object of this interface type
public class ShapeHolder
{
protected Shape shape=null;
...
}
JDO doesn't define how an interface is persisted in the datastore. Obviously there can be
many implementations and so no obvious solution. DataNucleus allows the following
-
per-implementation
: a FK is created for each implementation so that the datastore
can provide referential integrity. The other advantage is that since there are FKs then
querying can be performed. The disadvantage is that if there are many
implementations then the table can become large with many columns not used
-
identity
: a single column is added and this stores the class name of the
implementation stored, as well as the identity of the object. The disadvantages are that
no querying can be performed, and that there is no referential integrity.
-
xcalia
: a slight variation on "identity" whereby there is a single column
yet the contents of that column are consistent with what Xcalia XIC JDO implementation
stored there.
The user controls which one of these is to be used by specifying the
extension
mapping-strategy
on the field containing the interface.
The default is "per-implementation"
To allow persistence of this interface field with DataNucleus you have 2 levels of control.
The first level is global control. Since all of our
Square
,
Circle
,
Rectangle
classes
implements
Shape
then we just define them in the MetaData as we would normally.
<package name="com.mydomain.samples.shape">
<class name="Square">
...
</class>
<class name="Circle">
...
</class>
<class name="Rectangle">
...
</class>
</package>
The global way means that when mapping that field DataNucleus will look at all PersistenceCapable classes
it knows about that implement the specified interface.
JDO2 also allows users to specify a list of classes implementing the interface on a field-by-field
basis, defining which of these implementations are accepted for a particular interface
field. To do this you define the Meta-Data like this
<package name="com.mydomain.samples.shape">
<class name="ShapeHolder">
<field name="shape" persistence-modifier="persistent"
field-type="com.mydomain.samples.shape.Circle,
com.mydomain.samples.shape.Rectangle,
com.mydomain.samples.shape.Square"/>
</class>
That is, for any interface object in a class to be persisted, you define the possible implementation
classes that can be stored there. DataNucleus interprets this information and will map the above example
classes to the following in the database
So DataNucleus adds foreign keys from the containers table to all of the possible implementation tables for
the
shape
field.
If we use
mapping-strategy
of "identity" then we get a different datastore schema.
<class name="ShapeHolder">
<field name="shape" persistence-modifier="persistent">
<extension vendor-name="datanucleus" key="mapping-strategy" value="identity"/>
</field>
</class>
and the datastore schema becomes
and the column "SHAPE" will contain strings such as
com.mydomain.samples.shape.Circle:1
allowing retrieval of the related implementation
object.
You can have a Collection/Map containing elements of an interface type.
You specify this in the same way as you would any Collection/Map.
You can have a
Collection of interfaces as long as you use a join table relation and it is
unidirectional.
The "unidirectional" restriction is that the interface is not persistent on its own and
so cannot store the reference back to the owner object.
You need to use a DataNucleus extension tag "implementation-classes" if you want to
restrict the collection to only contain particular implementations of an interface.
Use the 1-N relationship guides for the metadata definition to use.
The default mapping strategy for interface fields and collections of interfaces is
to have separate FK column(s) for each possible implementation of the interface.
Obviously if you have an application where new implementations are added over time
the schema will need new FK column(s) adding to match. This is possible if you enable
the persistence property
datanucleus.rdbms.dynamicSchemaUpdates
, setting it
to
true
. With this set, any insert/update operation of an interface related field
will do a check if the implementation being stored is known about in the schema and,
if not, will update the schema accordingly.