Java/Hibernate/JPA:不能使用复合键 -> 瞬态对象持久化



我的问题是我无法保存我的实体,因为它包含另一个实体,由一个键映射,该键也是该表主键的一部分。该表如下所示:

table C:
+-----+------+
| id_A | id_B |
+-----+------+

..其中 idA 是表 A 的主键,带 EntityA,idB 是表 B 的主键,带 EntityB

所以它基本上是一个N到M的关系。这是我用于表 C 的实体:

@Entity
public class EntityC {
    private long idA;
    private EntityB b;
    @Id
    @Column(name = "id_A")
    public long getIdA() {
        return idA;
    }
    @Id
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "id_B")
    public EntityB getB() {
        return b;
    }
    ...setters are here...
}

请注意,id_A按原样映射(id),而id_B映射为其对象表示形式,EntityB 。这就是我想用它做的:

EntityC c = new EntityC();
c.setIdA(123);
c.setB(new EntityB());
em.persist(c);
tx.commit();
em.close();

只有当我能坚持EntityC时,我才想坚持EntityB.

tx.commit()我得到这个异常:org.hibernate.TransientObjectException: object references an unsaved transient instance

我想发生这种情况是因为部分主键 id_B 没有保存。但是我设置了级联,所以应该没有问题!

为什么这不起作用?


编辑:

当我这样做时:

em.persist(c.getB());
em.persist(c);

它有效。但是Hibernate/JPA不能自动做到这一点吗?我认为这就是级联的好处。


编辑2:

添加了一个嵌入式 Id 而不是 id_A 和 id_B:

@Embeddable
public class EntityCID implements Serializable {
public long idA;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "id_B", referencedColumnName = "id")
public EntryB b;
}

实体 C 现在如下所示:

@Entity
public class EntityC implements Serializable {
    private EntityCID id;
    ...
    @EmbeddedId
    public void getId() {
        return id;
    }
}

但是如果我在em.persist(c)之前不em.persist(c.getId().b);,我仍然会收到瞬态对象异常.坚持这一点,虽然它很丑。

@Trein:它不是双向的。实体 B 代码:

@Entity
public class EntityB implements Serializable {
    public long id;
    public String text;
}

如果你仔细想想,你所看到的是完全有道理的。

EntityC 是关系 C<>B 的"拥有方":它定义了 JoinColumn,实体 B 具有"mappedBy"属性。

因此,在保存 C 时,事件的顺序通常是:

  • 插入到 C/更新 C
  • 插入到 B/更新 B

现在,在您的情况下,这会导致问题,因为显然只有在首先保留 B 的情况下才能保存 C。

就您上面的陈述而言:我想坚持"只有当我可以坚持实体 C 时,才坚持实体 B"。怎么会这样呢?

JPA有一个"派生标识符"的概念,我不太熟悉,但是在Pro JPA一书中定义为在以下情况下发生:

当一个实体中的标识符包含另一个实体的外键时 实体,我们称之为派生标识符。因为包含 派生的标识符依赖于另一个实体的标识, 我们将第一个称为依赖实体。它所依赖的实体 on 是来自 依赖实体,称为父实体

现在,尽管最初的建议是你定义了两个@Id属性,这是错误的,但是似乎在 1-2-m 上有一个额外的@Id实际上在 JPA 2 中对这种情况是有效的。

本书提供了许多处理派生标识符的方法,但下面给出的一个示例看起来与您的情况非常相似。因此,您可能需要进一步调查@MapsId属性。

@Entity
public class Project {
@EmbeddedId private ProjectId id;
@MapsId("dept")
@ManyToOne
@JoinColumns({
@JoinColumn(name="DEPT_NUM", referencedColumnName="NUM"),
@JoinColumn(name="DEPT_CTRY", referencedColumnName="CTRY")})
private Department department;
// ...
}
@Embeddable
public class ProjectId implements Serializable {
@Column(name="P_NAME")
private String name;
@Embedded
private DeptId dept;
// ...
}

进一步查看:

如何在 Hibernate 3.6 中正确级联保存主键上的一对一双向关系

是双向关系吗?我建议您删除@Id getB()并执行修改:

@OneToOne(cascade = CascadeType.ALL, mappedBy = "id_B")
@PrimaryKeyJoinColumn(name = "id_B")
public EntityB getB() {
    return b;
}

您的实体类只能有一个使用 @Id 注释的属性。通常,当您需要它时,您可以创建一个将存储这两个属性的类,这将充当 Id 类。

你不能传递新的 Entity() 作为参考。因为它不会有任何值(甚至主键)。那么休眠如何将其作为外键插入表中。并且级联将保存您的父对象,如果它没有保存,无需为所有人调用保存方法。但是当你传递新对象时,它就不行了。

最新更新