更改保留其ID的实体类型

我使用hibernate作为持久层。 有两个实体位于同一个表中,扩展了一个具有单表inheritance策略的超类。

@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) public abstract class A { @Id @GeneratedValue protected Long id; // some common fields for B and C } @Entity public class B extends A { // B-specific fields } @Entity public class C extends A { // C-specific fields } 

我有一个id为4的B实例。 如何将此实例的类型更改为C保留其ID(4)?

 B b = em.find(B.class, 4L); C c = convertToC(b); c.setId(b.getId()); em.remove(b); em.persist(c); 

上面的代码失败了

 org.hibernate.PersistentObjectException: detached entity passed to persist: C 

有可能吗?

Hibernate试图使持久性尽可能透明 – 这意味着它试图遵循与普通Java对象相同的原则。 现在,用Java重写你的问题,你会得到:

如何将B类的实例转换为(不兼容的)C类的实例?

你知道答案 – 你做不到。 您可以创建C的实例并复制必要的属性,但B将始终为B,而不是C.因此,原始问题的答案是 – 无法通过JPA或Hibernate API完成。

但是,与普通Java不同,使用Hibernate可以作弊:-)使用@DiscriminatorColumn映射InheritanceType.SINGLE_TABLE并且为了将B转换为C,您需要将其值从B指定的任何内容更新为C的任何指定。技巧是 – 你不能用Hibernate API做到这一点; 你需要通过纯SQL来做到这一点。 但是,您可以将此更新语句映射为命名SQL查询,并使用Hibernate工具执行它。

因此,该算法是:

  1. 如果它在那里从会话中逐出 B(这很重要)
  2. 执行您的命名查询。
  3. 使用前B的id加载现在已知的C语言。
  4. 根据需要更新/设置属性。
  5. 坚持C.

在这种情况下,“c”是hibernate会话一无所知的对象,但它有一个ID,因此它假设该对象已被持久化。 在这种情况下,persist()毫无意义,因此失败了。

Hibernate Session.persist()的javadoc(我知道你没有使用Hibernate API,但是语义是相同的,而且hibernate文档更好)说“使一个瞬态实例持久化”。 如果您的对象已有ID,则不是短暂的。 相反,它认为它是一个分离的实例(即一个已经持久化但与当前会话无关的实例)。

我建议你尝试merge()而不是persist()。

您可以使用自己的ID(未生成)并执行以下操作:

  1. Retrive B
  2. 开放交易
  3. 删除B.
  4. 提交交易
  5. 打开一个新的交易
  6. 创建C并坚持下去
  7. 关闭第二笔交易

这样你就可以在重新插入C之前清除表中的id。

你如何区分表中的两个实体? 我假设你可以改变一些字段值(或值)使B成为C?

您可以使用一种方法来加载超类A并更改区分值并保存。 然后在你的下一个Hibernate Session中你的B将是一个C.

我认为skaffman就在这里,一旦id被设置它将不会持久,并且因为id被生成它期望序列负责分配id号。

你可能不会把id作为@GeneratedValue? 或者不同的生成器策略类型之一可能是为了避免合并生成新的序列值,但我怀疑这会有问题。