JPA。如何对现有实体进行子类化并保留其ID? [英] JPA. How do I subclass existing entity and keep its ID?

查看:107
本文介绍了JPA。如何对现有实体进行子类化并保留其ID?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有两个经典的非抽象JPA类:个人和学生。

  @Entity 
@继承(strategy = InheritanceType.JOINED)
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private String id;
// ...
}

@Entity
public class Student extends Person {
// ...
}

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

  student = new Student(); 
student.setPersonData(person.getPersonData());
student.setId(person.getId());
entityManager.persist(student);上面的代码产生'被分离的实体传递给持久化'异常,而使用 entityManager .merge(student)跳过分配的 id ,并用新的id创建Person和Student的两个新实体。任何想法如何保持原始的Id?

解决方案

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


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


此外,在使用连接的实体执行跨实体的表的情况下继承策略,身份在根类中单独定义。所有子类只存储该类的本地属性。



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



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



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



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



JPA维基手册在 对象转化 部分中讨论此情况。请注意在您的实体中提供类型属性而提供的具体建议,而不是使用继承来更改对象的类型。


转载



通常,已将
删除的对象移除,但在一些
的情况,您可能需要使对象
恢复生活。这通常会发生
与自然ID,而不是生成的
其中一个新的对象将总是得到一个
新的id。通常,
转换对象的愿望发生在
坏对象模型设计中,通常
希望更改
对象的类类型(在Java中无法完成)
,所以必须创建一个新的对象)。
通常最好的解决方案是
更改对象模型以使
对象保持一个类型对象,
定义其类型,而不是使用
继承。但是有时候,
轮回是可取的。



Assume I have two classic non-abstract JPA classes: Person and Student.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Person {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private String id;
  // ...
}

@Entity
public class Student extends Person {
  // ...
}

Now person having some id enters the university and becomes a student. How do I process that fact in JPA and keep person's id?

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

Code above produces 'Detached entity passed to persist' Exception, whereas using entityManager.merge(student) skips assigned id and creates two new entities of Person and Student with new id. Any ideas how can I keep original Id?

解决方案

The JPA specification prohibits an application from changing the identity of an entity (Section 2.4):

The application must not change the value of the primary key[10]. The behavior is undefined if this occurs.[11]

Also, in the case of tables where inheritance across entities is performed using the joined inheritance strategy, the identity is defined in the root class alone. All child classes only store the local attribute of the class.

By invoking student.setId(person.getId()); you are attempting to change the identity of the not-yet-persisted entity to that of an existing one. This in itself doesn't make sense, especially since you are generating values for the identities using the sequence generation strategy of AUTO (which is usually a TABLE).

If we were to ignore the previous points, and if you wish to convert the Person to a Student, without losing the identity, well that is more or less impossible (at least in a clean manner, as @axtavt has pointed out). The simple reason is that you cannot downcast successfully from a Person to a Student at runtime, for that is the natural object-oriented operation that you are attempting to perform in real life. Even if you downcast successfully in a hypothetical manner, the original entity has a discriminator column value that needs modification; without knowing how the JPA provider uses and caches this value, any attempt to make changes in the data, using native SQL might result in more trouble than it is worth.

If you are not averse to losing the generated IDs (after all one usually generates them, so that you could use natural keys or you didn't have to share these generated IDs publicly), you ought to create a copy of the Person object and recreate it as a Student. This will ensure that the JPA provider will also populate the discriminator column correctly. Also, you'll need to delete the original Person entity.

All of the above, is considering that you will not modify the current object model. If you can modify the object model, you do might a chance of retaining the original ID. This would require that you drop the inheritance hierarchy, for it does not naturally suit your domain in the first place. The attempt to downcast from a Person to a Student indicates that inheritance is not a natural fit. Following @axtavt's advice is more appropriate, for it actually means favoring composition over inheritance, if you read it carefully (at least I read it that way).

The JPA Wikibook discusses this situation in the section on Object Reincarnation. Note the specific advice provided on having a type attribute in your entity instead of using inheritance to change the type of the object.

Reincarnation

Normally an object that has been removed, stays removed, but in some cases you may need to bring the object back to life. This normally occurs with natural ids, not generated ones, where a new object would always get an new id. Generally the desire to reincarnate an object occurs from a bad object model design, normally the desire to change the class type of an object (which cannot be done in Java, so a new object must be created). Normally the best solution is to change your object model to have your object hold a type object which defines its type, instead of using inheritance. But sometimes reincarnation is desirable.

这篇关于JPA。如何对现有实体进行子类化并保留其ID?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆