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