JPA。 如何将现有实体子类化并保留其ID?

假设我有两个经典的非抽象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); 

上面的代码生成’传递给持久化的分离实体’exception,而使用entityManager.merge(student)跳过分配的id并创建具有新id的Person和Student的两个新实体。 任何想法如何保持原始ID?

JPA规范禁止应用程序更改实体的身份(第2.4节):

应用程序不得更改主键的值[10]。 如果发生这种情况,行为是不确定的。[11]

此外,对于使用联接inheritance策略执行实体间inheritance的表,仅在根类中定义标识。 所有子类仅存储类的本地属性。

通过调用student.setId(person.getId()); 您试图将尚未持久化的实体的身份更改为现有实体的身份。 这本身没有意义,特别是因为您使用AUTO的序列生成策略(通常是TABLE)为身份生成值。

如果我们忽略了之前的观点,并且如果你希望将Person转换为Student,而不会丢失身份,那么这或多或少是不可能的(至少以干净的方式,正如@axtavt指出的那样)。 原因很简单,您无法在运行时成功地从Person向学生转发,因为这是您尝试在现实生活中执行的自然面向对象操作。 即使您以假设的方式成功进行了预测,原始实体也有一个需要修改的鉴别器列值; 在不知道JPA提供程序如何使用和缓存此值的情况下,使用本机SQL进行数据更改的任何尝试都可能导致比实际值更多的麻烦。

如果您不反对丢失生成的ID(毕竟通常会生成它们,以便您可以使用自然键,或者您不必公开共享这些生成的ID),您应该创建Person对象的副本,将其重新创建为学生。 这将确保JPA提供程序也将正确填充鉴别器列。 此外,您还需要删除原始的Person实体。

以上所有,都在考虑您不会修改当前的对象模型。 如果您可以修改对象模型,则可能会保留原始ID。 这将要求您删除inheritance层次结构,因为它首先不一定适合您的域。 从一个人向学生倾斜的尝试表明inheritance不是天生的。 遵循@ axtavt的建议更合适,因为它实际上意味着有利于构图而不是inheritance,如果你仔细阅读它(至少我是这样读的)。

JPA Wikibook在Object Reincarnation一节中讨论了这种情况。 请注意有关在实体中具有type属性而不是使用inheritance来更改对象类型的具体建议。

投胎

通常情况下,已移除的对象会被移除,但在某些情况下,您可能需要将对象恢复生命。 这通常发生在自然的id中,而不是生成的id,其中新的对象总是会获得新的id。 通常,重新生成对象的愿望来自不良对象模型设计,通常是希望改变对象的类类型(不能用Java完成,因此必须创建新对象)。 通常,最好的解决方案是更改对象模型,让对象保存一个定义其类型的类型对象,而不是使用inheritance。 但有时轮回是可取的。

据我所知,没有好办法实现它。

如果您将此情境建模为可以具有多个角色的Role (一对多关系),其中Student是其中之一(即Student扩展Role ),则会更加简单。