4

考虑以下在实体 A 和 B 中具有循环引用的 Hibernate 3.6 实体映射:

@MappedSuperclass
abstract class Entity {

  @Id
  protected UUID id = UUID.randomUUID();

  @Version
  protected Integer revision;
} 

@Entity
class A extends Entity {
  // not null in the database
  @OneToOne(optional = false)
  B b;
}

@Entity
class B extends Entity {
  // not null in the database
  @ManyToOne(optional = false)
  A a;
}

实体的 id 是在创建新实例时生成的,因此它在任何 SQL INSERT 之前设置。要确定实例是否是瞬态的,请Interceptor使用:

class EntityInterceptor extends EmptyInterceptor {

  @Override
  public boolean isTransient(Object entity) {
    return ((Entity)entity).getRevision == null;
  }
}

当我尝试保存(在单个事务中)A 和 B 的实例(引用设置为彼此)时,Hibernate 失败并出现TransientObjectException(对象引用了未保存的瞬态实例 - 在刷新之前保存瞬态实例)。

A a = new A();
B b = new B();
a.setB(b);
b.setA(a);

// start transaction
sessionFactory.getCurrentSession().saveOrUpdate(a); // a before b or vice versa doesn't matter
sessionFactory.getCurrentSession().saveOrUpdate(b);
sessionFactory.getCurrentSession().flush();
// commit

当我将映射更改为 Ab 和 Ba 的级联保存时,Hibernate 为 A 生成以下 SQL INSERT 语句:

INSERT INTO A (id, revision, b) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 0, NULL);

这违反了NOT NULL对 Ab 的约束并导致 a ConstraintViolationException。尽管 B 的 id 在插入时是已知的,但它并未在 SQL INSERT 中设置。在数据库(PostgreSQL 9.1)中,定义了对 Ab 的 FK 约束DEFERRABLE INITIALLY DEFERRED,因此如果设置了 Ab,则以下 INSERT 语句将运行而不会出现任何错误:

START TRANSACTION;
INSERT INTO A (id, revision, b) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 0, 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb');
INSERT INTO B (id, revision, a) VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 0, 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa');
COMMIT;

当我在数据库中删除对 Ab 的 NOT NULL 约束并保持级联保存映射时,上述代码以保存 A 和 B 工作正常。 编辑PostgreSQL 中NOT NULL不能推迟约束,只能推迟 FK 约束。 结束编辑 老实说,在这种情况下,我没有查看生成的 SQL 语句(我现在无法重现它),但我想它看起来像这样:

START TRANSACTION;
INSERT INTO A (id, revision, b) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 0, NULL);
INSERT INTO B (id, revision, a) VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 0, 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa');
UPDATE A SET b = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' WHERE id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
COMMIT;

我知道对于我在这里尝试做的事情可能有更好的实体设计,但我真的很想知道是否有办法保持NOT NULL约束(如果可能的话,还有没有级联保存的原始映射) 并使我的原始代码工作。有没有办法告诉Hibernate 只用 Ab = B.id 插入 A,即使 B 在插入 A 时是瞬态的?一般来说,我找不到任何关于 Hibernate 和延迟 FK 约束的文档,所以任何关于它的指针都会受到赞赏。

4

1 回答 1

1

您在这里拥有的是经典的双向关系。双方相互参照。如果你有双向的一侧需要标记为更强的一侧,这是通过使用mappedBy 或 inverse 来标记灯芯侧来完成的。此外,您有 FK 约束,因此需要定义 not-null 为真,以便休眠将插入 FK。

看这个完整的。

于 2012-08-04T16:25:19.670 回答