【问题标题】:Changing the type of an entity preserving its ID更改保留其 ID 的实体的类型
【发布时间】:2010-11-10 06:31:00
【问题描述】:

我使用休眠作为持久层。有 2 个实体存在于同一个表中,通过单表继承策略扩展了一个超类。

@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

有可能吗?

【问题讨论】:

  • 我已将例外添加到问题中
  • 只是一种预感,但是您是否尝试过调用 merge() 而不是 persist()?
  • 是的,merge 给 c 一个新的 id。
  • 这可能是 Entitymanager API 不足的一个例子。它不像原生 Hibernate API 那样富有表现力,而且有时意义不大。
  • 有没有办法在原生 Hibernate API 中做到这一点?

标签: java hibernate persistence


【解决方案1】:

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

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

你知道答案——你不知道。您可以创建 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 加载 what-is-now-known-as-C。
  4. 根据需要更新/设置属性。
  5. 坚持C

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 感谢您解释异常原因,我会注意的。合并不会失败,而是给“c”一个新的 id,忽略已经设置的内容。
    【解决方案3】:

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

    1. 检索 B
    2. 开启交易
    3. 删除B
    4. 提交事务
    5. 开启新交易
    6. 创建 C 并将其持久化
    7. 关闭第二笔交易

    通过这种方式,您将在将其重新插入为 C 之前从表中清除 id。

    【讨论】:

    • 这将很难实现。我所有的实体都使用自动递增的 ID。有没有办法强制生成 id 的值?
    【解决方案4】:

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

    您可以有一个方法来加载超类 A 并更改区别值并保存。然后在您的下一个 Hibernate 会话中,您的 B 将成为 C。

    【讨论】:

    • Hibernate 使用鉴别器列来确定在加载数据时需要实例化哪个类。列的值等于类的简单名称。您是否建议使用纯 SQL 来修改表数据?我没有看到其他方法可以按照您的建议进行操作。
    【解决方案5】:

    我认为 skaffman 就在这里,一旦设置了 id,它就不会持续存在,而且由于生成了 id,它期望序列负责分配 id 号。

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

    【讨论】:

      猜你喜欢
      • 2014-08-21
      • 2011-09-19
      • 1970-01-01
      • 2015-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-29
      相关资源
      最近更新 更多