JPA. Как я могу подклассифицировать существующий объект и сохранить его идентификатор?

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

@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 с новым идентификатором. Любые идеи, как я могу сохранить исходный идентификатор?

Ответ 1

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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