Josh
Josh

Reputation: 1774

Hibernate not correctly cascading bi-directional relationship

I am having trouble with a one-to-one bi-directional relationship I am trying to create in Hibernate. When creating and saving a new child and parent, it tries persisting the child with a null reference to the parent. Looking at the hibernate logs, it generates the ids for both tables, then sets the child's reference to the parent as null and performs the query. I am utterly confused.

In the parent's annotated class, I have:

@OneToOne
@JoinColumn(name = "CHILD_ID")
@Cascade(CascadeType.ALL)
private Child child;

In the child's annotated class, I have:

@OneToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;

For creating/persisting, I have:

Parent p = new Parent();
Child c = new Child();
p.setChild(c);
c.setParent(p);
getHibernateTemplate().save(p);

Hibernate logs, with interesting lines in bold:

    [org.springframework.orm.hibernate3.SessionFactoryUtils] [Opening Hibernate Session]
    [org.hibernate.impl.SessionImpl] [opened session at timestamp: 13313040615]
    [org.hibernate.event.def.DefaultSaveOrUpdateEventListener] [saving transient instance]
    [org.hibernate.jdbc.AbstractBatcher] [opening JDBC connection]
    [org.hibernate.SQL] [select UNQE_KEY_VALU from  where UNQE_KEY_NAME = '' for update]
    [org.hibernate.jdbc.AbstractBatcher] [closing JDBC connection (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
    [org.hibernate.id.MultipleHiLoPerTableGenerator] [new hi value: 1413946]
    [org.hibernate.event.def.AbstractSaveEventListener] [generated identifier: 14139460, using strategy: org.hibernate.id.MultipleHiLoPerTableGenerator]
    [org.hibernate.event.def.AbstractSaveEventListener] [saving [Parent#14139460]]
    [org.hibernate.engine.Cascade] [processing cascade ACTION_SAVE_UPDATE for: Parent]
    [org.hibernate.engine.CascadingAction] [cascading to saveOrUpdate: Child]
    [org.hibernate.engine.IdentifierValue] [id unsaved-value: 0]
    [org.hibernate.event.def.AbstractSaveEventListener] [transient instance of: Child]
    [org.hibernate.event.def.DefaultSaveOrUpdateEventListener] [saving transient instance]
    [org.hibernate.jdbc.AbstractBatcher] [opening JDBC connection]
    [org.hibernate.SQL] [select UNQE_KEY_VALU from  where UNQE_KEY_NAME = '' for update]
    [org.hibernate.jdbc.AbstractBatcher] [closing JDBC connection (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
    [org.hibernate.id.MultipleHiLoPerTableGenerator] [new hi value: 1413947]
    [org.hibernate.event.def.AbstractSaveEventListener] [generated identifier: 14139470, using strategy: org.hibernate.id.MultipleHiLoPerTableGenerator]
    [org.hibernate.event.def.AbstractSaveEventListener] [saving [Child#14139470]]
    [org.hibernate.engine.Cascade] [done processing cascade ACTION_SAVE_UPDATE for: Child]
    [org.hibernate.engine.Cascade] [processing cascade ACTION_SAVE_UPDATE for: Parent]
    [org.hibernate.engine.Cascade] [done processing cascade ACTION_SAVE_UPDATE for: Parent]
    [org.springframework.orm.hibernate3.HibernateTemplate] [Eagerly flushing Hibernate session]
    [org.hibernate.event.def.AbstractFlushingEventListener] [flushing session]
    [org.hibernate.event.def.AbstractFlushingEventListener] [processing flush-time cascades]
    [org.hibernate.engine.Cascade] [processing cascade ACTION_SAVE_UPDATE for: Parent]
    [org.hibernate.engine.CascadingAction] [cascading to saveOrUpdate: Child]
    [org.hibernate.event.def.AbstractSaveEventListener] [persistent instance of: Child]
    [org.hibernate.event.def.DefaultSaveOrUpdateEventListener] [ignoring persistent instance]
    [org.hibernate.event.def.DefaultSaveOrUpdateEventListener] [object already associated with session: [Child#14139470]]
    [org.hibernate.engine.Cascade] [done processing cascade ACTION_SAVE_UPDATE for: Parent]
    [org.hibernate.event.def.AbstractFlushingEventListener] [dirty checking collections]
    [org.hibernate.event.def.AbstractFlushingEventListener] [Flushing entities and processing referenced collections]
    [org.hibernate.persister.entity.AbstractEntityPersister] [Child.parent is dirty]
    [org.hibernate.event.def.DefaultFlushEntityEventListener] [Updating entity: [Child#14139470]]
    [org.hibernate.event.def.AbstractFlushingEventListener] [Processing unreferenced collections]
    [org.hibernate.event.def.AbstractFlushingEventListener] [Scheduling collection removes/(re)creates/updates]
    [org.hibernate.event.def.AbstractFlushingEventListener] [Flushed: 2 insertions, 1 updates, 0 deletions to 2 objects]
    [org.hibernate.event.def.AbstractFlushingEventListener] [Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections]
    [org.hibernate.pretty.Printer] [listing entities:]
    [org.hibernate.pretty.Printer] [Parent{child=Child#14139470, parentId=14139460}]
    [org.hibernate.pretty.Printer] [Child{parent=Parent#14139460, childId=14139470}]
    [org.hibernate.event.def.AbstractFlushingEventListener] [executing flush]
    [org.hibernate.jdbc.ConnectionManager] [registering flush begin]
    [org.hibernate.persister.entity.AbstractEntityPersister] [Inserting entity: [Child#14139470]]
    [org.hibernate.jdbc.AbstractBatcher] [about to open PreparedStatement (open PreparedStatements: 0, globally: 0)]
    [org.hibernate.jdbc.ConnectionManager] [opening JDBC connection]
    [org.hibernate.SQL] [insert into CHILD_TABLE (PARENT_ID, CHILD_ID) values (?, ?)]
    [org.hibernate.jdbc.AbstractBatcher] [preparing statement]
    [org.hibernate.persister.entity.AbstractEntityPersister] [Dehydrating entity: [Child#14139470]]
    [org.hibernate.type.LongType] [binding null to parameter: 1]
    [org.hibernate.type.LongType] [binding '14139470' to parameter: 2]
    [org.hibernate.persister.entity.AbstractEntityPersister] [Inserting entity: [Parent#14139460]]
    [org.hibernate.jdbc.AbstractBatcher] [Executing batch size: 1]
    [org.hibernate.jdbc.AbstractBatcher] [about to close PreparedStatement (open PreparedStatements: 1, globally: 1)]
    [org.hibernate.jdbc.AbstractBatcher] [closing statement]
    [DEBUG] [org.hibernate.util.JDBCExceptionReporter] [Could not execute JDBC batch update [insert into CHILD_TABLE (PARENT_ID, CHILD_ID) values (?, ?)]]
java.sql.BatchUpdateException: ORA-01400: cannot insert NULL into ("CHILD_TABLE"."PARENT_ID")

In the first two bold lines, the parent knows the child's id and vice versa. However, when generating the child's sql, it sets the parent's id as null. What is going on?

Edit: As reference, I am using the following hibernate jars: org.hibernate:hibernate-annotations:jar:3.3.1.GA org.hibernate:hibernate:jar:3.2.6.ga

Upvotes: 0

Views: 2650

Answers (2)

Josh
Josh

Reputation: 1774

I fixed it, though I'm not sure why it works. I moved the cascade from Parent to Child and save the Child instead of the Parent.

In the parent's annotated class, I have:

@OneToOne
@JoinColumn(name = "CHILD_ID")
private Child child;

In the child's annotated class, I have:

@OneToOne
@JoinColumn(name = "PARENT_ID")
@Cascade(CascadeType.ALL)
private Parent parent;

For creating/persisting, I have:

Parent p = new Parent();
Child c = new Child();
p.setChild(c);
c.setParent(p);
getHibernateTemplate().save(c);

Upvotes: 0

JB Nizet
JB Nizet

Reputation: 692181

I don't know exactly why you get this behavior, but what I know is that you don't have a bidirectional one-to-one association here.

What you have is two different one-to-one unidirectional associations: the parent knows its child using a foreign key to the child table, and the child knows its parent using a foreign key to the parent table.

BTW, this way of doing could lead to a situation where a parent A has a child B which has a parent C.

If what you actually want is a bidirectional one-to-one association, you should remove one of the foreign keys, and use the other one to map the association. Suppose you keep the CHILD_ID column in the parent table, the mapping would then be:

@OneToOne
@JoinColumn(name = "CHILD_ID")
@Cascade(CascadeType.ALL)
private Child child;

and

@OneToOne(mappedBy = "child")
private Parent parent;

Upvotes: 6

Related Questions