Как JPA обрабатывает частичное, непересекающееся наследование (используя InheritanceType.JOINED в сценарии «класс на таблицу») вместе с EntityManager.find ()? - PullRequest
3 голосов
/ 11 ноября 2010

У меня проблема моделирования / понимания частичное , несвязанное наследование с аннотациями 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 лучше всего справляется с этой моделью?

Ответы [ 2 ]

5 голосов
/ 11 ноября 2010

Кроме того, на 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 и экземпляр Coach с тем же id в контексте постоянства, вы не можете иметь несколько объектов, представляющих одну и ту же строку базы данных , это нарушит управление состоянием JPA модель.

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

Ссылки

1 голос
/ 11 ноября 2010

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

Что, вероятно, сработает лучше, так это отделить персонажа от роли и иметь один класс Person, содержащий Set, и иметь Coach, Referee и Player с расширенной ролью вместо Person.

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

...