diff --git a/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/DerivedIdentitySimpleParentSimpleDepMapsIdTest.java b/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/DerivedIdentitySimpleParentSimpleDepMapsIdTest.java index 3889428c24..db168e4aa8 100644 --- a/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/DerivedIdentitySimpleParentSimpleDepMapsIdTest.java +++ b/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/DerivedIdentitySimpleParentSimpleDepMapsIdTest.java @@ -1,6 +1,10 @@ package org.hibernate.test.annotations.derivedidentities.e4.b; import java.util.Date; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; import org.hibernate.Session; import org.hibernate.test.annotations.TestCase; @@ -61,6 +65,45 @@ public class DerivedIdentitySimpleParentSimpleDepMapsIdTest extends TestCase { s.close(); } + public void testExplicitlyAssignedDependentIdAttributeValue() { + // even though the id is by definition generated (using the "foreign" strategy), JPA + // still does allow manually setting the generated id attribute value which providers + // are expected to promptly disregard :? + Session s = openSession(); + s.beginTransaction(); + Person person = new Person( "123456789" ); + MedicalHistory medicalHistory = new MedicalHistory( "987654321", person ); + s.persist( person ); + s.persist( medicalHistory ); + s.getTransaction().commit(); + s.close(); + + // again, even though we specified an id value of "987654321" prior to persist, + // Hibernate should have replaced that with the "123456789" from the associated + // person + assertEquals( person.ssn, medicalHistory.patient.ssn ); + assertEquals( person, medicalHistory.patient ); + assertEquals( person.ssn, medicalHistory.id ); + + s = openSession(); + s.beginTransaction(); + // Should return null... + MedicalHistory separateMedicalHistory = (MedicalHistory) s.get( MedicalHistory.class, "987654321" ); + assertNull( separateMedicalHistory ); + // Now we should find it... + separateMedicalHistory = (MedicalHistory) s.get( MedicalHistory.class, "123456789" ); + assertNotNull( separateMedicalHistory ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.beginTransaction(); + s.delete( medicalHistory ); + s.delete( person ); + s.getTransaction().commit(); + s.close(); + } + @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/MedicalHistory.java b/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/MedicalHistory.java index 9dced978d6..fefeb70944 100644 --- a/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/MedicalHistory.java +++ b/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/MedicalHistory.java @@ -1,6 +1,7 @@ package org.hibernate.test.annotations.derivedidentities.e4.b; import java.util.Date; +import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; @@ -16,11 +17,20 @@ import javax.persistence.TemporalType; public class MedicalHistory { @Id String id; // overriding not allowed ... // default join column name is overridden @MapsId + + @MapsId + @JoinColumn(name = "FK") + @OneToOne(cascade= CascadeType.ALL) + Person patient; + @Temporal(TemporalType.DATE) Date lastupdate; - @JoinColumn(name = "FK") - @MapsId - @OneToOne - Person patient; + public MedicalHistory() { + } + + public MedicalHistory(String id, Person patient) { + this.id = id; + this.patient = patient; + } } diff --git a/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/Person.java b/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/Person.java index e476242444..42ed1e1f4c 100644 --- a/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/Person.java +++ b/annotations/src/test/java/org/hibernate/test/annotations/derivedidentities/e4/b/Person.java @@ -10,4 +10,11 @@ import javax.persistence.Id; public class Person { @Id String ssn; + + public Person() { + } + + public Person(String ssn) { + this.ssn = ssn; + } } \ No newline at end of file diff --git a/core/src/main/java/org/hibernate/event/def/DefaultPersistEventListener.java b/core/src/main/java/org/hibernate/event/def/DefaultPersistEventListener.java index 4d05ae777e..22a79602b6 100755 --- a/core/src/main/java/org/hibernate/event/def/DefaultPersistEventListener.java +++ b/core/src/main/java/org/hibernate/event/def/DefaultPersistEventListener.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * Copyright (c) 2010, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are - * distributed under license by Red Hat Middleware LLC. + * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU @@ -20,7 +20,6 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - * */ package org.hibernate.event.def; @@ -32,10 +31,12 @@ import org.hibernate.HibernateException; import org.hibernate.ObjectDeletedException; import org.hibernate.PersistentObjectException; import org.hibernate.engine.CascadingAction; +import org.hibernate.engine.EntityEntry; import org.hibernate.event.EventSource; import org.hibernate.event.PersistEvent; import org.hibernate.event.PersistEventListener; import org.hibernate.engine.SessionImplementor; +import org.hibernate.id.ForeignGenerator; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; @@ -48,7 +49,6 @@ import org.hibernate.util.IdentityMap; * @author Gavin King */ public class DefaultPersistEventListener extends AbstractSaveEventListener implements PersistEventListener { - private static final Logger log = LoggerFactory.getLogger(DefaultPersistEventListener.class); /** @@ -69,19 +69,18 @@ public class DefaultPersistEventListener extends AbstractSaveEventListener imple * @throws HibernateException */ public void onPersist(PersistEvent event, Map createCache) throws HibernateException { - final SessionImplementor source = event.getSession(); final Object object = event.getObject(); final Object entity; - if (object instanceof HibernateProxy) { + if ( object instanceof HibernateProxy ) { LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer(); if ( li.isUninitialized() ) { - if ( li.getSession()==source ) { + if ( li.getSession() == source ) { return; //NOTE EARLY EXIT! } else { - throw new PersistentObjectException("uninitialized proxy passed to persist()"); + throw new PersistentObjectException( "uninitialized proxy passed to persist()" ); } } entity = li.getImplementation(); @@ -89,32 +88,58 @@ public class DefaultPersistEventListener extends AbstractSaveEventListener imple else { entity = object; } - - int entityState = getEntityState( - entity, - event.getEntityName(), - source.getPersistenceContext().getEntry(entity), - source - ); - - switch (entityState) { - case DETACHED: - throw new PersistentObjectException( - "detached entity passed to persist: " + - getLoggableName( event.getEntityName(), entity ) - ); + + final String entityName; + if ( event.getEntityName() != null ) { + entityName = event.getEntityName(); + } + else { + entityName = source.bestGuessEntityName( entity ); + event.setEntityName( entityName ); + } + + final EntityEntry entityEntry = source.getPersistenceContext().getEntry( entity ); + int entityState = getEntityState( entity, entityName, entityEntry, source ); + if ( entityState == DETACHED ) { + // JPA 2, in its version of a "foreign generated", allows the id attribute value + // to be manually set by the user, even though this manual value is irrelevant. + // The issue is that this causes problems with the Hibernate unsaved-value strategy + // which comes into play here in determining detached/transient state. + // + // Detect if we have this situation and if so null out the id value and calculate the + // entity state again. + + // NOTE: entityEntry must be null to get here, so we cannot use any of its values + EntityPersister persister = source.getFactory().getEntityPersister( entityName ); + if ( ForeignGenerator.class.isInstance( persister.getIdentifierGenerator() ) ) { + if ( log.isDebugEnabled() ) { + if ( persister.getIdentifier( entity, source ) != null ) { + log.debug( "Resetting entity id attribute to null for foreign generator" ); + } + } + persister.setIdentifier( entity, null, source ); + entityState = getEntityState( entity, entityName, entityEntry, source ); + } + } + + switch ( entityState ) { + case DETACHED: + throw new PersistentObjectException( + "detached entity passed to persist: " + + getLoggableName( event.getEntityName(), entity ) + ); case PERSISTENT: - entityIsPersistent(event, createCache); + entityIsPersistent( event, createCache ); break; case TRANSIENT: - entityIsTransient(event, createCache); + entityIsTransient( event, createCache ); break; - default: - throw new ObjectDeletedException( - "deleted entity passed to persist", - null, + default: + throw new ObjectDeletedException( + "deleted entity passed to persist", + null, getLoggableName( event.getEntityName(), entity ) - ); + ); } }