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

Osw*_*Osw 10 java ejb jpa jpa-2.0

假设我有两个经典的非抽象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 {
  // ...
}
Run Code Online (Sandbox Code Playgroud)

现在有一些身份证的人进入大学并成为一名学生.我如何在JPA中处理这个事实并保持个人身份?

student = new Student();
student.setPersonData(person.getPersonData());
student.setId(person.getId());
entityManager.persist(student);
Run Code Online (Sandbox Code Playgroud)

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

Vin*_*lds 5

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

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

此外,对于使用连接的继承策略执行跨实体的继承的表,仅在根类中定义标识.所有子类仅存储类的本地属性.

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

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

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

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

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

投胎

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