4.9. Polymorphic Relationships

A polymorphic relationship is one where the referenced type could be any entity. For example, a class that has a property of type java.lang.Object would be polymorphic; this property - if persisted - could point to any object in the database.

More typically though polymorphic relationships arise when we have decoupled two classes so that one references the other through an interface. Since there could be many implementors of the interface, we again have a polymorphic relationship.

This is one area where we hit a limitation of relational databases. A foreign key requires a primary key to reference. However, it isn't practical to map every domain class as a member of an inheritance hierarchy with java.lang.Object as its root. Consider: if we used the JOINED strategy, every retrieval would involve a query against the object table and then down to the subclass' table.

Instead we abandon the use of referential integrity in the database. Instead, we just store the object reference in two parts: by identifying the concrete class of the referenced object, and the identifier of the referenced object. Note how this is the same tuple discussed in Section 2.5, “Specify a discriminator”.

The approach mandated by JPA Objects unifies two relating requirements: that of discriminating concrete classes within an inheritance hierarchy, and of discriminating instances throughout the entire database.

In practical terms, we map a polymorphic relationship using the @org,hibernate.annotations.Any for a reference to a single object, and @javax.persistence.ManyToAny annotation for a colleciton of references. For example, suppose we have a Vehicle can be owned either by an individual Person or be owned by a Company (such as a fleet car). In the domain model both Person and Company implement VehicleOwner:

@Entity
@DiscriminatorValue("PRS")
public class Person implements VehicleOwner { ... }

and

@Entity
@DiscriminatorValue("CPY")
public class Company implements VehicleOwner { ... }

In the Vehicle class we use @Any along with @AnyMetaDef to identify these concrete implementations:

@Entity
@DiscriminatorValue("VEH")
public class Vehicle {
    ...

    @Any(
       metaColumn=@Column(name="owner_type" , length=3),
       fetch=FetchType.LAZY
    )
    @AnyMetaDef(
       idType="long", metaType="string" ,
       metaValues={
          @MetaValue(targetEntity=Person.class, value="PRS" ),
          @MetaValue(targetEntity=Company.class, value="CPY" )
       }
    )
    @JoinColumn(name="owner_id" )
    public VehicleOwner getOwner() { ... }
    public void setOwner(VehicleOwner owner) { ... }

    ...    
}

In the vehicle table (for Vehicle class) this will give rise to a two-part tuple (owner_type, owner_id), that collectively identifies the object that is acting as a VehicleOwner. The owner_type takes the value "PRS" for Person, in which case the owner_id contains a person Id from the person table; if it takes "CPY" then owner_id contains the company Id from the company table. Note that this tuple is, in effect, the JpaOid for the object (again, see Section 2.5, “Specify a discriminator”).

The @org.hibernate.annotations.ManyToAny similarly has a slew of annotions. If for example a Vehicle could have multiple owners, we would have:

@Entity
@DiscriminatorValue("VEH")
public class Vehicle {
    ...

    @ManyToAny(
        metaColumn = @Column( name = "owner_type" )
    )
    @AnyMetaDef(
        idType = "integer", metaType = "string",
        metaValues = {
            @MetaValue( targetEntity = Person.class, value="PRS" ),
            @MetaValue( targetEntity = Company.class, value="CPY" ) 
        }
    )
    @Cascade( { org.hibernate.annotations.CascadeType.ALL } )
    @JoinTable(
        name = "vehicle_owners", 
        joinColumns = @JoinColumn( name = "vehicle_id" ),
        inverseJoinColumns = @JoinColumn( name = "owner_id" )
    )
    public List<Property> getOwners() { ... }
    private void setOwners(List<VehicleOwner> owners) { ... }

    ...
}

This would give rise to a vehicle_owners link table, whose columns would be (vehicle_id, owner_type, owner_id). The vehicle_id identifies the Vehicle whose owners we are interested in; the owner_type, owner_id together identify the owner (either Person or Company).