JPA. Как мне создать подкласс существующего объекта и сохранить его идентификатор? - PullRequest
10 голосов
/ 21 июня 2011

Предположим, у меня есть два классических неабстрактных класса JPA: Персона и Студент.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Person {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private String id;
  // ...
}

@Entity
public class Student extends Person {
  // ...
}

Теперь человек, имеющий какой-то идентификатор, поступает в университет и становится студентом. Как мне обработать этот факт в JPA и сохранить идентификатор человека?

student = new Student();
student.setPersonData(person.getPersonData());
student.setId(person.getId());
entityManager.persist(student);

Приведенный выше код создает исключение «Отдельная сущность, переданная для сохранения», тогда как использование entityManager.merge(student) пропускает назначенное id и создает две новые сущности Person и Student с новым идентификатором. Любые идеи, как сохранить оригинальный идентификатор?

Ответы [ 2 ]

5 голосов
/ 21 июня 2011

Спецификация JPA запрещает приложению изменять личность объекта (Раздел 2.4):

Приложение не должно изменять значение первичного ключа [10]. Поведение не определено, если это происходит. [11]

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

Вызывая student.setId(person.getId());, вы пытаетесь изменить идентичность еще не сохраненной сущности на идентичность существующей. Само по себе это не имеет смысла, тем более что вы генерируете значения для идентификаторов, используя стратегию генерации последовательности AUTO (обычно это ТАБЛИЦА).

Если бы мы проигнорировали предыдущие пункты, и если вы хотите преобразовать Человека в Студента, не теряя при этом личности, то это более или менее невозможно (по крайней мере, чисто, как указывал @axtavt). ). Простая причина заключается в том, что вы не можете успешно прекратить переход от Человека к Студенту во время выполнения, поскольку это естественная объектно-ориентированная операция, которую вы пытаетесь выполнить в реальной жизни. Даже если вы успешно уклонились в гипотетическом порядке, исходная сущность имеет значение столбца дискриминатора, которое необходимо изменить; не зная, как поставщик JPA использует и кэширует это значение, любая попытка внести изменения в данные с использованием собственного SQL может привести к большему количеству проблем, чем оно того стоит.

Если вы не против потери сгенерированных идентификаторов (в конце концов, обычно их генерируют, чтобы вы могли использовать естественные ключи или вам не нужно было делиться этими сгенерированными идентификаторами публично), вам следует создать копию Персона возражает и воссоздает его как ученика. Это гарантирует, что поставщик JPA также правильно заполнит столбец дискриминатора. Также вам необходимо удалить исходную сущность Person.

Все вышеперечисленное, учитывая, что вы не будете изменять текущую объектную модель. Если вы можете изменить объектную модель, у вас может быть шанс сохранить исходный идентификатор. Это потребует, чтобы вы отбросили иерархию наследования, поскольку она изначально не подходит вашему домену. Попытка понизить рейтинг «Человек до ученика» указывает на то, что наследование не является естественным соответствием. Следование совету @ axtavt более уместно, поскольку на самом деле оно означает предпочтение композиции перед наследованием, если вы читаете ее внимательно (по крайней мере, я так читал).

JPA Wikibook обсуждает эту ситуацию в разделе Реинкарнация объекта . Обратите внимание на конкретный совет относительно наличия атрибута type в вашей сущности вместо использования наследования для изменения типа объекта.

Реинкарнация

Обычно объект, который был удален, остается удаленным, но в некоторых случаях вам может понадобиться принести объект вернуться к жизни. Это обычно происходит с естественными идентификаторами, а не сгенерированными, где новый объект всегда получит новый идентификатор Вообще желание реинкарнировать объект происходит из плохой объектный дизайн, обычно желание изменить тип класса объект (что не может быть сделано в Java, поэтому новый объект должен быть создан). Обычно лучшим решением является изменить вашу объектную модель, чтобы ваш объект содержит тип объекта, который определяет его тип, вместо использования наследование. Но иногда реинкарнация желательна.

4 голосов
/ 21 июня 2011

Насколько я знаю, нет хороших способов добиться этого.

Было бы намного проще, если бы вы смоделировали эту ситуацию как Person, которая может иметь несколько ролей Role s (отношение один-ко-многим), где Student является одной из них (то есть расширяется Student Role).

...