4

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.

tscho
  • 2,024
  • 15
  • 15
  • Sounds *a lot* like [this related case of the chicken-egg-problem](http://stackoverflow.com/questions/8394177/complex-foreign-key-constraint-in-sqlalchemy/8395021#8395021). The second part of that answer may be of use here. – Erwin Brandstetter Jun 29 '12 at 20:08
  • Also, the PostgreSQL documentation of the behaviour of constraints that are defined `DEFERRABLE` is lacking currently, as we found out [here](http://stackoverflow.com/q/10032272/939860). – Erwin Brandstetter Jun 29 '12 at 20:20
  • @ErwinBrandstetter: (as always) a very good answer to the chicken-egg problem! I am certainly not struggling with the database part of my problem, but I haven't thought of the *back-referencing* foreign key idiom yet. Really nice, it will definitely be there when my As and Bs go live ;) I don't know SQLAlchemy, maybe I need the equivalent of "I've gotten around the circular relationship using use_alter on choice_id, and post_update on SystemVariables' choice relationship" for the Hibernate part of my problem. Maybe an SQLAlchemy expert can tell me if this also covers my not-null issue... – tscho Jun 29 '12 at 22:02
  • Sadly, I am neither a Hiberante nor an SA expert. You'll need someone else to lend a hand. – Erwin Brandstetter Jun 29 '12 at 22:24
  • I am having a very similar issue with this on Oracle 11gR2. See [here](http://stackoverflow.com/questions/12610914/session-flush-not-working-as-expected) – Andy Oct 01 '12 at 15:36

1 Answers1

1

What you have here is classical bidirectional relationship. both side has reference to each other. If you have bidirectonal one side need to be marked as the stronger side this is done by ussing mappedBy or inverse to mark the wick side. In addtional you have FK constraints , so need to define that not-null is true in order that hibernate will insert with the FK.

look on this for complete.

Community
  • 1
  • 1
Avihai Marchiano
  • 3,837
  • 3
  • 38
  • 55