I have a problem modeling/understanding partial, non-disjoint inheritance with JPA annotations.
Here are the four tables:
CREATE TABLE Persons (
id INTEGER NOT NULL,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
PRIMARY KEY (id));
CREATE TABLE Referees (
id INTEGER NOT NULL,
license_nbr VARCHAR(10) DEFAULT NULL NULL,
PRIMARY KEY (id),
CONSTRAINT referees_persons_fk
FOREIGN KEY (id)
REFERENCES Persons (id)
ON DELETE CASCADE
ON UPDATE CASCADE);
CREATE TABLE Coaches (
id INTEGER NOT NULL,
license_nbr VARCHAR(10) DEFAULT NULL NULL,
PRIMARY KEY (id),
CONSTRAINT coaches_persons_fk
FOREIGN KEY (id)
REFERENCES Persons (id)
ON DELETE CASCADE
ON UPDATE CASCADE);
CREATE TABLE Players (
id INTEGER NOT NULL,
registration_nbr VARCHAR(20) DEFAULT NULL NULL,
PRIMARY KEY (id),
CONSTRAINT players_persons_fk
FOREIGN KEY (id)
REFERENCES Persons (id)
ON DELETE CASCADE
ON UPDATE CASCADE);
My shortened entity classes are:
@Entity
@Table(name = "Persons")
@Inheritance(strategy = InheritanceType.JOINED)
public class Person implements Serializable
{
@Id
@Column(name = "id")
private Integer id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
...
}
@Entity
@Table(name = "Referees")
public class Referee extends Person implements Serializable
{
@Column(name = "license_nbr")
private String licenseNbr = null;
...
}
@Entity
@Table(name = "Coaches")
public class Coach extends Person implements Serializable
{
@Column(name = "license_nbr")
private String licenseNbr = null;
...
}
@Entity
@Table(name = "Players")
public class Player extends Person implements Serializable
{
@Column(name = "registration_nbr")
private String registrationNbr = null;
...
}
Partial inheritance: person entities can exist without any corresponding referee, coach, or player entities
Non-disjoint inheritance: there can be any number of corresponding sub entities, that is a person can be a referee, coach, and player at the same time, possibly any combination of the three. Each sub table can only have exactly none or one entity, but not multiple.
Note that because the inheritance is partial, Person isn't abstract. Furthermore, there's no discriminator on Person/s because the inheritance is non-disjoint.
Is such a model possible in Java ORMs? How would the annotations look like?
I have problems finding instances the way I annotated the classes using:
// Michael Jordan: the person who's only a player, too
// EclipseLink 2.1.1 ( 1): ?
// EclipseLink 2.2.0 ( 1): ?
// Hibernate 3.6 (-1): wrong Player instance
Person pe1 = em.find(Person.class, 1);
System.out.println("Loaded person = " + pe1);
// EclipseLink 2.1.1 ( 1): ?
// EclipseLink 2.2.0 ( 1): ?
// Hibernate 3.6 (+1): correct Player instance
Player pl1 = em.find(Player.class, 1);
System.out.println("Loaded player = " + pl1);
// EclipseLink 2.1.1 ( 1): ?
// EclipseLink 2.2.0 ( 1): ?
// Hibernate 3.6 (+1): correct null
Referee re1 = em.find(Referee.class, 1);
System.out.println("Loaded referee = " + re1);
// EclipseLink 2.1.1 ( 1): ?
// EclipseLink 2.2.0 ( 1): ?
// Hibernate 3.6 (+1): correct null
Coach ch1 = em.find(Coach.class, 1);
System.out.println("Loaded coach = " + ch1);
System.out.println();
System.out.println();
// Dirk Nowitzki: the person who's also a player *and* a referee
// EclipseLink 2.1.1 ( 1): ?
// EclipseLink 2.2.0 ( 1): ?
// Hibernate 3.6 (-1): wrong Player instance
Person pe2 = em.find(Person.class, 2);
System.out.println("Loaded person = " + pe2);
// EclipseLink 2.1.1 ( 1): ?
// EclipseLink 2.2.0 ( 1): ?
// Hibernate 3.6 (+1): correct Player instance
Player pl2 = em.find(Player.class, 2);
System.out.println("Loaded player = " + pl2);
// EclipseLink 2.1.1 ( 1): ?
// EclipseLink 2.2.0 ( 1): ?
// Hibernate 3.6 (-1): wrong null
Referee re2 = em.find(Referee.class, 2);
System.out.println("Loaded referee = " + re2);
// EclipseLink 2.1.1 ( 1): ?
// EclipseLink 2.2.0 ( 1): ?
// Hibernate 3.6 (+1): correct null
Coach ch2 = em.find(Coach.class, 2);
System.out.println("Loaded coach = " + ch2);
System.out.println();
System.out.println();
// Blake Griffin: the person who's also a player, referee, *and* coach
// EclipseLink 2.1.1 ( 1): ?
// EclipseLink 2.2.0 ( 1): ?
// Hibernate 3.6 (-1): wrong Player instance
Person pe3 = em.find(Person.class, 3);
System.out.println("Loaded person = " + pe3);
// EclipseLink 2.1.1 ( 1): ?
// EclipseLink 2.2.0 ( 1): ?
// Hibernate 3.6 (+1): correct Player instance
Player pl3 = em.find(Player.class, 3);
System.out.println("Loaded player = " + pl3);
// EclipseLink 2.1.1 ( 1): ?
// EclipseLink 2.2.0 ( 1): ?
// Hibernate 3.6 (-1): wrong null
Referee re3 = em.find(Referee.class, 3);
System.out.println("Loaded referee = " + re3);
// EclipseLink 2.1.1 ( 1): ?
// EclipseLink 2.2.0 ( 1): ?
// Hibernate 3.6 (-1): wrong null
Coach ch3 = em.find(Coach.class, 3);
System.out.println("Loaded coach = " + ch3);
EclipseLink 2.1.1 and 2.2.0 nightly build fail completely on EntityManager.find(...), while Hibernate at least returns some of the expected instances. Note the word "expected" here. It's my understanding of what a JPA ORM ought to do, but I might be mistaken.
As you can see, Hibernate always returns sub class instances of Player, but is unable to find Coach and Referee instances if a Player instance is also available.
So, how does JPA best handle this model?
Furthermore, there's no discriminator on Person/s because the inheritance is non-disjoint.
To my knowledge, the inheritance with JPA is strict i.e. an entity belongs to a single hierarchy (a Person is either a Player or a Referee, not both, so there is only one corresponding record in the sub tables). In other words, I don't think non-disjoint inheritance is supported (nothing in the JPA specification says it is by the way) or even possible (more on this later).
And whether you use a discriminator with a JOINED
strategy or not doesn't mean that inheritance is non-disjoint or not, that's just used as an hint by some persistence provider.
Actually, while the JPA specification suggests (in section 11.1.10 "DiscriminatorColumn Annotation") that one should be able to use a Discriminator
with a JOINED
strategy, Hibernate doesn't even support it, although this was possible using XML mappings, as reported in ANN-140. The JPA wiki book sum it up like this:
Some JPA providers support joined inheritance with or without a discriminator column, some require the discriminator column, and some do not support the discriminator column. So joined inheritance does not seem to be fully standardized yet.
If you look at the SQL generated by Hibernate you'll see that it performs an outer join
mixed with a case
to detect and instantiate the "right" type. For example, given a Project
with a SmallProject
and a LargeProject
as subclasses, a from Project
would give:
select
project0_.id as id66_,
project0_.name as name66_,
project0_1_.budget as budget67_,
case
when project0_1_.id is not null then 1
when project0_2_.id is not null then 2
when project0_.id is not null then 0
else -1
end as clazz_
from
Project project0_
left outer join
LARGEPROJECT project0_1_
on project0_.id=project0_1_.id
left outer join
SMALLPROJECT project0_2_
on project0_.id=project0_2_.id
Obviously, the above would be problematic with non-disjoint inheritance i.e. rows in both child tables for a same ID.
But regardless of how Hibernate implemented the JOINED
strategy, I think non-disjoint inheritance is more generally just not possible given how the persistence context works, you can't have a Player
instance and a Coach
instance with the same id
in the persistence context, you can't have multiple objects representing the same row of the database, this would break JPA's state management model.
PS: I'm surprised the first test Person pe1 = em.find(Person.class, 1);
failed with Hibernate. It definitely works with earlier versions. I wonder "what" Player
instance you get.