How do I properly cascade save a one-to-one, bidirectional relationship on primary key in Hibernate 3.6
hibernate, java, jpa, jpa-2.0, orm
Solution
Specification says that derived entity should be the owning side of the relationship:
2.4.1 Primary Keys Corresponding to Derived Identities
The identity of an entity may be derived from the identity of another entity (the "parent" entity) when the former entity (the "dependent" entity) is the owner of a many-to-one or one-to-one relationship to the parent entity and a foreign key maps the relationship from dependent to parent.
In your case `LeadAffiliate ` is derived, so it should be the owner, when `Lead` should be marked as non-owning side by `mappedBy`. The following works in both 3.5.0 and 3.5.6:
public class Lead {
@Id @GeneratedValue
private Long leadId;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "lead")
private LeadAffiliate leadAffiliate;
...
}
.
public class LeadAffiliate {
@Id
private Long leadId;
@OneToOne @MapsId
private Lead lead;
...
}
Problem
I have an one-to-one, bidirectional entity relationship with shared keys. When I attempt to save the owner of the association I get a "null id generated" exception against the owned side of the relationship. I am utilizing hibernate-entitymanager and using spring for transaction management. Owning Entity ``` @Entity @Table(name = "lead") public class Lead { private Long leadId; private LeadAffiliate leadAffiliate; @Id @GeneratedValue(strategy = GenerationType.AUTO) public Long getLeadId() { return leadId; } @OneToOne(cascade = CascadeType.ALL) @PrimaryKeyJoinColumn public LeadAffiliate getLeadAffiliate() { return leadAffiliate; } } ``` Owned Entity ``` @Entity @Table(name = "lead_affiliate") public class LeadAffiliate { private Long leadId; private Lead lead; @Id public Long getLeadId() { return leadId; } @MapsIdmappedBy = "leadAffiliate") @OneToOne(cascade = CascadeType.All) @PrimaryKeyJoinColumn @JoinColumn(name = "lead_id") public Lead getLead() { return lead; } } ``` and the code below is being used to save the entity: ``` LeadAffiliate aff = new LeadAffiliate(); aff.setLead(lead); lead.setLeadAffiliate(aff); em.persist(lead); ``` This all works perfectly fine in hibernate 3.5.0-Final. When attempting to upgrade to 3.5.6-Final or 3.6.0.Final is when I start getting the "null id generated for LeadAffiliate" error: ``` javax.persistence.PersistenceException: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.sellingsource.bizdev.entities.LeadAffiliate at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1214) at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1147) at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1153) at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:678) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:365) at $Proxy152.persist(Unknown Source) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240) at $Proxy120.persist(Unknown Source) at com.sellingsource.common.dao.JpaGenericDao.create(JpaGenericDao.java:38) ... 64 more Caused by: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.sellingsource.bizdev.entities.LeadAffiliate at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:123) at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:69) at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:179) at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135) at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:799) at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:791) at org.hibernate.engine.EJB3CascadingAction$1.cascade(EJB3CascadingAction.java:48) at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:392) at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335) at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204) at org.hibernate.engine.Cascade.cascade(Cascade.java:161) at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:450) at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:282) at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:203) at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:129) at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:69) at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:179) at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135) at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61) at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:808) at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:782) at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:786) at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:672) ... 77 more ``` As an aside, I am not sure that the annotations on Lead Affiliate were quite right to begin with. They worked, but seemed kind of kludgey. So I have since changed them to: ``` @Entity @Table(name = "lead_affiliate") public class LeadAffiliate { private Long leadId; private Lead lead; @Id @GenericGenerator(name = "foreign", strategy = "foreign", parameters = { @org.hibernate.annotations.Parameter(name = "property", value="lead") }) @GeneratedValue(generator = "foreign") public Long getLeadId() { return leadId; } @OneToOne(mappedBy = "leadAffiliate") @PrimaryKeyJoinColumn public Lead getLead() { return lead; } } ``` However, with these changes I get the same result. (Works in 3.5.0 but not 3.5.6 or 3.6.0) Is there a new way I need to be doing this or is this a bug? My concern is that my code is currently working because of a bug :/.