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

Posted

技术标签:

【中文标题】JPA。如何子类化现有实体并保留其 ID?【英文标题】:JPA. How do I subclass existing entity and keep its ID? 【发布时间】:2011-09-19 06:26:49 【问题描述】:

假设我有两个经典的非抽象 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 中处理该事实并保留人员 ID?

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

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

【问题讨论】:

我确定它不会创建“2 个新实体”......只是它在每个表中放置了一行,毕竟这是 JOINED 继承的内容,相当于一个学生对象 @DataNucleus 2 个实体意味着人和学生都可以通过新 ID 找到。但如果它能让我找到解决方案或解释为什么它不可能,我很容易同意你的看法。 如果任何 JPA 实现通过 find(cls, 1) 调用返回一个 Student(1) 和一个 Person(1),那么它就有一个错误。数据库中的内容是它的另一面。根据下面的回复,没有简单的方法可以实现您的目标 【参考方案1】:

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

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

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

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

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

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

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

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

转世

通常是一个对象 移除,保持移除,但在某些情况下 您可能需要携带物品的情况 起死回生。这通常会发生 使用自然 ID,而不是生成的 ID, 一个新对象总是会得到一个 新身份证。一般的愿望是 转世一个对象从一个 糟糕的对象模型设计,通常是 希望改变班级类型 对象(不能在 Java 中完成, 所以必须创建一个新对象)。 通常最好的解决方案是 更改您的对象模型以拥有您的 对象持有一个类型对象 定义它的类型,而不是使用 遗产。但是有时 轮回是可取的。

【讨论】:

感谢您的回复,接受它,但“......继承不是天生的”是有问题的。无法想象比 John Smith 更自然的对象是人或学生和人的实例。其中,好的一点是修改我的身份策略,谢谢。 是的,这种说法是主观的。我的观点是,当你不能以自然的方式对对象模型本身进行操作时,那么模型在某种程度上已经被破坏了。我尝试编写一个解决方案,那时我发现向下转换是表示您试图在此处建模的域事件的唯一简洁方式。答案中当然说明了详细的方式-根据原始对象创建一个新的域对象,然后删除原始对象。这涉及以特定方式使用数据模型。【参考方案2】:

据我所知,没有什么好的方法可以实现它。

如果你将这种情况建模为一个Person,它可以具有多个角色Roles(一对多关系),其中Student 是其中之一(即Student 扩展Role)。

【讨论】:

谢谢,已经在考虑更改模型或使用本机查询,这对我来说似乎完全荒谬。 Home 其他人会对此发表评论。

以上是关于JPA。如何子类化现有实体并保留其 ID?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 JPA Hibernate 映射中将 GUID(不是 PK)添加到已经具有 PK(整数)的现有实体

如何通过 JPA 查询按子类实体值获取父实体?

如何使用 JPA 获取最后一个持久化实体的 ID

如何在 JPA 中映射名称为保留字的实体字段

如何使用 Spring Data JPA 保存具有手动分配标识符的实体?

如何持久化大量实体(JPA)