Consider the following Hibernate 3.6 entity mapping with a circular reference in entities A and 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;
}
The id of the entities is generated when a new instance is created, so it is set prior to any SQL INSERT.
To determine whether an instance is transient or not an Interceptor is used:
class EntityInterceptor extends EmptyInterceptor {
@Override
public boolean isTransient(Object entity) {
return ((Entity)entity).getRevision == null;
}
}
When I try to save (in a single transaction) instances of A and B (with the references set to each other) Hibernate fails with a TransientObjectException (object references an unsaved transient instance - save the transient instance before flushing).
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
When I change the mapping to cascading save of A.b and B.a Hibernate generates the following SQL INSERT statement for A:
INSERT INTO A (id, revision, b) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 0, NULL);
This violates the NOT NULL constraint on A.b and causes a ConstraintViolationException.
Even though the id of B is known at insert time it is not set in the SQL INSERT.
In the database (PostgreSQL 9.1) the FK constraint on A.b is defined DEFERRABLE INITIALLY DEFERRED, so if A.b was set the following INSERT statements would run without any errors:
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;
When I drop the NOT NULL constraint on A.b in the database and keep the cascading save mapping the above code to save A and B works fine.
EDIT
The NOT NULL constraint cannot be deferred in PostgreSQL, only the FK constraint can be deferred.
END EDIT
To be honest I didn't take a look at the generated SQL statements in this case (and I cannot reproduce it right now) but I suppose it looks like this:
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;
I know that there may be a better entity design for what I am trying to do here in the first place, but I would really like to know if there is a way to keep the NOT NULL constraint (and if possible also the original mapping without cascading save) and make my original code work.
Is there a way to tell Hibernate to just insert A with A.b = B.id even though B is transient at insert time of A?
Generally, I couldn't find any documentation concerning Hibernate and deferred FK constraints, so any pointers on that appreciated.