更改保留其ID的实体类型

时间:2009-07-13 11:08:34

标签: java hibernate persistence

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

@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

有可能吗?

5 个答案:

答案 0 :(得分:11)

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

  

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

你知道答案 - 你做不到。你可以创建一个 new 的C实例并复制必要的属性,但是B 总是是B,而不是C.因此你原来问题的答案是 - 它无法完成通过JPA或Hibernate API。

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

因此,算法是:

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

答案 1 :(得分:2)

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

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

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

答案 2 :(得分:0)

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

  1. Retrive B
  2. open transaction
  3. 删除B
  4. 提交交易
  5. 开启新交易
  6. 创建C并坚持
  7. 关闭第二笔交易
  8. 通过这种方式,您可以在将表重新插入C之前清除表中的id。

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

我认为skaffman就在这里,一旦设置了id就不会持久,而且因为生成了id,所以它希望序列负责分配id号。

您可能无法将ID设为@GeneratedValue?或者不同的生成器策略类型之一可能是为了避免合并产生新的序列值,但我怀疑这会有问题。