JPA : Interface Fields

JPA doesn't define support for persisting fields of type interface, but DataNucleus provides an extension whereby the implementations of the interface are persistable objects. 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 let's 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;
    ...
}
            

DataNucleus allows the following strategies for mapping this field

  • 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 advantage is that if you have large numbers of implementations then this can cope with no schema change. 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"

In terms of the implementations of the interface, you can either leave the field to accept any known about implementation, or you can restrict it to only accept some implementations (see "implementation-classes" metadata extension). If you are leaving it to accept any persistable implementation class, then you need to be careful that such implementations are known to DataNucleus at the point of encountering the interface field. By this we mean, DataNucleus has to have encountered the metadata for the implementation so that it can allow for the implementation when handling the field. You can force DataNucleus to know about a persistable class by using an autostart mechanism, or using persistence.xml, or by placement of the package.jdo file so that when the owning class for the interface field is encountered so is the metadata for the implementations.


1-1

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 implement Shape then we just define them in the MetaData as we would normally.

public class Square implement Shape
{
    ...
}
public class Circle implement Shape
{
    ...
}
public class Rectangle implement Shape
{
    ...
}

The global way means that when mapping that field DataNucleus will look at all Entities it knows about that implement the specified interface.

DataNucleus 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

@Entity
public class ShapeHolder
{
    @OneToOne
    @Extension(key="implementation-classes",
        value="mydomain.Circle,mydomain.Rectangle,mydomain.Square")
    Shape shape;

    ...
}

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.

@Entity
public class ShapeHolder
{
    @OneToOne
    @Extensions(
        @Extension(key="implementation-classes",
            value="mydomain.Circle,mydomain.Rectangle,mydomain.Square"),
        @Extension(key="mapping-strategy", value="identity")})
    Shape shape;

    ...
}

and the datastore schema becomes

and the column "SHAPE" will contain strings such as mydomain.Circle:1 allowing retrieval of the related implementation object.

1-N

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. Use the 1-N relationship guides for the metadata definition to use.

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. For example

@Entity
public class ShapeHolder
{
    @OneToMany
    @JoinTable
    @Extensions(
        @Extension(key="implementation-classes",
            value="mydomain.Circle,mydomain.Rectangle,mydomain.Square"),
        @Extension(key="mapping-strategy", value="identity")})
    Collection<Shape> shapes;

    ...
}

So the shapes field is a Collection of mydomain.Shape and it will accept the implementations of type Circle, Rectangle, Square and Triangle. If you omit the implementation-classes tag then you have to give DataNucleus a way of finding the metadata for the implementations prior to encountering this field.

Dynamic Schema Updates

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.