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
).