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 :/.
Bidirectional @OneToMany Relationship – Employer/EmployeeWhen you traverse from the “Many” side to the “One” side, you only need to make reference to one object, which is why the Employee class holds a single reference to an Employer class via the private Employer employer instance variable.
Bidirectional association allows us to fetch details of dependent object from both side. In such case, we have the reference of two classes in each other. Let's take an example of Employee and Address, if Employee class has-a reference of Address and Address has a reference of Employee.
JPA Relationships can be either unidirectional or bidirectional. This simply means we can model them as an attribute on exactly one of the associated entities or both. Defining the direction of the relationship between entities has no impact on the database mapping.
The meaning of CascadeType. ALL is that the persistence will propagate (cascade) all EntityManager operations ( PERSIST, REMOVE, REFRESH, MERGE, DETACH ) to the relating entities. It seems in your case to be a bad idea, as removing an Address would lead to removing the related User .
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; ... }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With