【发布时间】:2012-07-01 07:09:22
【问题描述】:
考虑以下 Hibernate 3.6 实体映射,在实体 A 和 B 中具有循环引用:
@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
当我将映射更改为 A.b 和 B.a 的 级联保存 时,Hibernate 会为 A 生成以下 SQL INSERT 语句:
INSERT INTO A (id, revision, b) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 0, NULL);
这违反了 A.b 上的 NOT NULL 约束并导致 ConstraintViolationException。
即使 B 的 id 在插入时已知,它也没有在 SQL INSERT 中设置。
在数据库(PostgreSQL 9.1)中,A.b 上的 FK 约束定义为 DEFERRABLE INITIALLY DEFERRED,因此如果设置了 A.b,则以下 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;
当我在数据库中删除 A.b 上的 NOT NULL 约束并保持 级联保存 映射上述代码以保存 A 和 B 时工作正常。
编辑
NOT NULL 约束在 PostgreSQL 中不能被推迟,只有 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 只用 A.b = B.id 插入 A,即使 B 在插入 A 时是瞬态的?
一般来说,我找不到任何关于 Hibernate 和延迟 FK 约束的文档,所以任何关于它的指针都值得赞赏。
【问题讨论】:
-
听起来很多像this related case of the chicken-egg-problem。该答案的第二部分可能在这里有用。
-
此外,我们发现 here 中定义的约束行为的 PostgreSQL 文档目前缺乏。
-
@ErwinBrandstetter:(一如既往)对鸡蛋问题的一个很好的回答!我当然没有为我的问题的数据库部分而苦苦挣扎,但我还没有想到 back-reference 外键成语。真的很好,当我的 As 和 B 上线时,它肯定会在那里 ;) 我不知道 SQLAlchemy,也许我需要相当于“我已经使用 select_id 上的 use_alter 和 SystemVariables 选择上的 post_update 解决了循环关系关系”对于我的问题的休眠部分。也许 SQLAlchemy 专家可以告诉我这是否也涵盖了我的非空问题...
-
很遗憾,我既不是 Hiberante 也不是 SA 专家。你需要别人帮忙。
-
我在 Oracle 11gR2 上遇到了非常相似的问题。见here
标签: hibernate postgresql foreign-keys hibernate-mapping