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

时间:2011-06-21 06:47:10

标签: java jpa ejb 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 {
  // ...
}

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

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

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

2 个答案:

答案 0 :(得分:5)

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

  

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

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

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

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

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

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

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

  

<强>轮回

     

通常是一个已经存在的对象   删除,保持删除,但在一些   您可能需要携带物品的情况   起死回生。这通常发生   天生的ids,不是生成的,   一个新对象永远得到的地方   新的身份证。一般的愿望   重生一个对象发生在一个   坏对象模型设计,通常是   希望改变一个类的类型   对象(不能用Java完成,   所以必须创建一个新对象)。   通常最好的解决方案是   改变你的对象模型让你的   对象持有一个类型对象   定义其类型,而不是使用   遗产。但有时候   轮回是可取的。

答案 1 :(得分:4)

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

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