考虑以下在实体 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 约束的文档,所以任何关于它的指针都会受到赞赏。