エンティティ 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 の制約に違反し、を引き起こします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 では制約を延期することはできません
。FKNOT NULL
制約のみを延期できます。
END EDIT
正直に言うと、この場合に生成された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
制約を維持する方法があるかどうかを本当に知りたいです (可能であれば、カスケード保存なしの元のマッピングも) ) を作成し、元のコードを機能させます。B が A の挿入時に一時的であっても、Hibernate に Ab = B.id で A を挿入するように指示する方法はありますか? 一般に、Hibernate および deferred FK 制約に関するドキュメントは見つかりませんでした。