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.