Как JPA обрабатывает частичное непересекающееся наследование (используя InheritanceType.JOINED в сценарии класса для таблицы) вместе с EntityManager.find()?

У меня проблема с моделированием/пониманием частичного, нераздельного наследования с аннотациями JPA.

Вот четыре таблицы:

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

Мои сокращенные классы сущностей:

@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;

    ...
}

Частичное наследование: сущности-лица могут существовать без соответствующих сущностей-судей, тренеров или игроков.

Непересекающееся наследование: может быть любое количество соответствующих подсущностей, то есть человек может быть судьей, тренером и игроком одновременно, возможно, в любой комбинации из трех. Каждая подтаблица может иметь ровно ни одного объекта или только один объект, но не несколько.

Обратите внимание, что поскольку наследование является частичным, Person не является абстрактным. Более того, у Person/s нет дискриминатора, потому что наследование не является непересекающимся.

Возможна ли такая модель в ORM Java? Как должны выглядеть аннотации?

У меня проблемы с поиском экземпляров, как я аннотировал классы, используя:

// 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 и 2.2.0 полностью терпит неудачу в EntityManager.find(...), в то время как Hibernate по крайней мере возвращает некоторые из ожидаемых экземпляров. Обратите внимание на слово «ожидается». Это мое понимание того, что должен делать JPA ORM, но я могу ошибаться.

Как видите, Hibernate всегда возвращает экземпляры подкласса Player, но не может найти экземпляры Coach и Referee, если экземпляр Player также доступен.

Итак, как JPA лучше всего справляется с этой моделью?


person Kawu    schedule 10.11.2010    source источник


Ответы (2)


Более того, у Person/s нет дискриминатора, потому что наследование не является непересекающимся.

Насколько мне известно, наследование с JPA является строгим, т. е. сущность принадлежит к одной иерархии (человек является либо игроком, либо судьей, а не обоими, поэтому в подтаблицах есть только одна соответствующая запись). ). Другими словами, я не думаю, что нераздельное наследование поддерживается (между прочим, ничего в спецификации JPA не говорится) или даже возможно (подробнее об этом позже). ).

И независимо от того, используете ли вы дискриминатор со стратегией JOINED или нет, это не означает, что наследование является недисъюнктным или нет, это просто используется как подсказка некоторым поставщиком постоянства.

На самом деле, хотя спецификация JPA предполагает (в разделе 11.1.10 «Аннотация DiscriminatorColumn»), что можно использовать Discriminator со стратегией JOINED, Hibernate даже не поддерживает ее, хотя это было возможно с использованием XML-сопоставлений, как сообщалось. в ANN-140. Вики-книга JPA резюмирует это так:

Некоторые поставщики JPA поддерживают объединенное наследование со столбцом дискриминатора или без него, некоторые требуют столбец дискриминатора, а некоторые не поддерживают столбец дискриминатора. Таким образом, объединенное наследование, похоже, еще не полностью стандартизировано.

Если вы посмотрите на SQL, сгенерированный Hibernate, вы увидите, что он выполняет outer join, смешанный с case, для обнаружения и создания экземпляра «правильного» типа. Например, учитывая Project с SmallProject и LargeProject в качестве подклассов, from Project даст:

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

Очевидно, что описанное выше будет проблематичным с нераздельным наследованием, т. е. строками в обеих дочерних таблицах для одного и того же идентификатора.

Но независимо от того, как Hibernate реализовал стратегию JOINED, я думаю, что нераздельное наследование в более общем смысле просто невозможно, учитывая то, как работает контекст персистентности, у вас не может быть Player instance и экземпляр Coach с одним и тем же id в контексте постоянства, вы не можете иметь несколько объектов, представляющих одну и ту же строку базы данных, это нарушит модель управления состоянием JPA.

PS: я удивлен, что первый тест Person pe1 = em.find(Person.class, 1); провалился с Hibernate. Это определенно работает с более ранними версиями. Интересно, "какой" Player экземпляр у вас получится.

использованная литература

  • JPA 2.0 specification
    • Section 2.12 "Inheritance Mapping Strategies"
    • Раздел 11.1.10 «Аннотация столбца дискриминатора»
    • Раздел 11.1.20 «Аннотация наследования»
  • JPA Wiki Book
person Pascal Thivent    schedule 11.11.2010
comment
Таким образом, решение без изменения модели данных, которое является правильным, состояло бы в том, чтобы преобразовать все непересекающиеся отношения наследования в обычные ассоциации один к одному, используя то, что JPA знает как производный идентификатор? - person Kawu; 11.11.2010
comment
@Kawu Да, индивидуальная ассоциация с общим первичным ключом позволит сопоставить эту модель. - person Pascal Thivent; 11.11.2010

Я думаю, что JPA и Java вообще не справятся с этой иерархией классов.

Что, вероятно, будет работать лучше, так это отделить человека от роли и иметь один класс Person, содержащий набор, и чтобы тренер, рефери и игрок расширяли роль вместо человека.

Тогда Роль и подклассы, вероятно, могут быть сопоставлены более обычным способом, поскольку они образуют непересекающуюся иерархию, а лицо, имеющее несколько ролей, представлено таким образом, что его можно хранить в одном объекте Person в Java, с ролями, отображаемыми как коллекция в JPA.

person Don Roby    schedule 11.11.2010