From: <hib...@li...> - 2006-07-21 16:54:29
|
Author: ste...@jb... Date: 2006-07-21 12:53:54 -0400 (Fri, 21 Jul 2006) New Revision: 10132 Added: branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/OptLockEntity.hbm.xml branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/TimestampedEntity.java branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/VersionedEntity.java Modified: branches/Branch_3_2/Hibernate3/src/org/hibernate/event/def/DefaultMergeEventListener.java branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/MergeTest.java Log: port fix for HHH-1927 to 3.2 branch Modified: branches/Branch_3_2/Hibernate3/src/org/hibernate/event/def/DefaultMergeEventListener.java =================================================================== --- branches/Branch_3_2/Hibernate3/src/org/hibernate/event/def/DefaultMergeEventListener.java 2006-07-21 16:52:30 UTC (rev 10131) +++ branches/Branch_3_2/Hibernate3/src/org/hibernate/event/def/DefaultMergeEventListener.java 2006-07-21 16:53:54 UTC (rev 10132) @@ -17,6 +17,8 @@ import org.hibernate.event.MergeEvent; import org.hibernate.event.MergeEventListener; import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.EntityEntry; +import org.hibernate.engine.EntityKey; import org.hibernate.intercept.FieldInterceptionHelper; import org.hibernate.intercept.FieldInterceptor; import org.hibernate.persister.entity.EntityPersister; @@ -85,14 +87,33 @@ event.setResult(entity); } else { - event.setEntity(entity); - - int entityState = getEntityState( - entity, - event.getEntityName(), - source.getPersistenceContext().getEntry(entity), - source - ); + event.setEntity( entity ); + int entityState = -1; + + // Check the persistence context for an entry relating to this + // entity to be merged... + EntityEntry entry = source.getPersistenceContext().getEntry( entity ); + if ( entry == null ) { + EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); + Serializable id = persister.getIdentifier( entity, source.getEntityMode() ); + if ( id != null ) { + EntityKey key = new EntityKey( id, persister, source.getEntityMode() ); + Object managedEntity = source.getPersistenceContext().getEntity( key ); + entry = source.getPersistenceContext().getEntry( managedEntity ); + if ( entry != null ) { + // we have specialized case of a detached entity from the + // perspective of the merge operation. Specifically, we + // have an incoming entity instance which has a corresponding + // entry in the current persistence context, but registered + // under a different entity instance + entityState = DETACHED; + } + } + } + + if ( entityState == -1 ) { + entityState = getEntityState( entity, event.getEntityName(), entry, source ); + } switch (entityState) { case DETACHED: @@ -263,13 +284,50 @@ } private boolean isVersionChanged(Object entity, EventSource source, EntityPersister persister, Object target) { - return persister.isVersioned() && - !persister.getVersionType().isSame( - persister.getVersion( target, source.getEntityMode() ), - persister.getVersion( entity, source.getEntityMode() ), - source.getEntityMode() - ); + if ( ! persister.isVersioned() ) { + return false; + } + // for merging of versioned entities, we consider the version having + // been changed only when: + // 1) the two version values are different; + // *AND* + // 2) The target actually represents database state! + // + // This second condition is a special case which allows + // an entity to be merged during the same transaction + // (though during a seperate operation) in which it was + // originally persisted/saved + boolean changed = persister.getVersionType().isSame( + persister.getVersion( target, source.getEntityMode() ), + persister.getVersion( entity, source.getEntityMode() ), + source.getEntityMode() + ); + + // TODO : perhaps we should additionally require that the incoming entity + // version be equivalent to the defined unsaved-value? + return changed && existsInDatabase( target, source, persister ); } + + private boolean existsInDatabase(Object entity, EventSource source, EntityPersister persister) { + EntityEntry entry = source.getPersistenceContext().getEntry( entity ); + if ( entry == null ) { + Serializable id = persister.getIdentifier( entity, source.getEntityMode() ); + if ( id != null ) { + EntityKey key = new EntityKey( id, persister, source.getEntityMode() ); + Object managedEntity = source.getPersistenceContext().getEntity( key ); + entry = source.getPersistenceContext().getEntry( managedEntity ); + } + } + + if ( entry == null ) { + // perhaps this should be an exception since it is only ever used + // in the above method? + return false; + } + else { + return entry.isExistsInDatabase(); + } + } protected void copyValues( final EntityPersister persister, Modified: branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/MergeTest.java =================================================================== --- branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/MergeTest.java 2006-07-21 16:52:30 UTC (rev 10131) +++ branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/MergeTest.java 2006-07-21 16:53:54 UTC (rev 10132) @@ -9,6 +9,7 @@ import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.NonUniqueObjectException; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.criterion.Projections; @@ -22,6 +23,58 @@ public MergeTest(String str) { super(str); } + + public void testPersistThenMergeInSameTxnWithVersion() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + VersionedEntity entity = new VersionedEntity( "test", "test" ); + s.persist( entity ); + s.merge( new VersionedEntity( "test", "test-2" ) ); + + try { + // control operation... + s.saveOrUpdate( new VersionedEntity( "test", "test-3" ) ); + fail( "saveOrUpdate() should fail here" ); + } + catch( NonUniqueObjectException expected ) { + // expected behavior + } + + tx.commit(); + s.close(); + + s = openSession(); + tx = s.beginTransaction(); + s.delete( entity ); + tx.commit(); + s.close(); + } + + public void testPersistThenMergeInSameTxnWithTimestamp() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + TimestampedEntity entity = new TimestampedEntity( "test", "test" ); + s.persist( entity ); + s.merge( new TimestampedEntity( "test", "test-2" ) ); + + try { + // control operation... + s.saveOrUpdate( new TimestampedEntity( "test", "test-3" ) ); + fail( "saveOrUpdate() should fail here" ); + } + catch( NonUniqueObjectException expected ) { + // expected behavior + } + + tx.commit(); + s.close(); + + s = openSession(); + tx = s.beginTransaction(); + s.delete( entity ); + tx.commit(); + s.close(); + } public void testMergeDeepTree() { @@ -330,7 +383,7 @@ } protected String[] getMappings() { - return new String[] { "ops/Node.hbm.xml", "ops/Employer.hbm.xml" }; + return new String[] { "ops/Node.hbm.xml", "ops/Employer.hbm.xml", "ops/OptLockEntity.hbm.xml" }; } public static Test suite() { Added: branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/OptLockEntity.hbm.xml =================================================================== --- branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/OptLockEntity.hbm.xml 2006-07-21 16:52:30 UTC (rev 10131) +++ branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/OptLockEntity.hbm.xml 2006-07-21 16:53:54 UTC (rev 10132) @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<!DOCTYPE hibernate-mapping PUBLIC + "-//Hibernate/Hibernate Mapping DTD 3.0//EN" + "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> + +<!-- + +--> + +<hibernate-mapping package="org.hibernate.test.ops"> + + <class name="VersionedEntity" table="V_ENTITY"> + <id name="id" column="ID" type="string"> + <generator class="assigned"/> + </id> + <version name="version" column="VERS" type="long" /> + <property name="name" column="NAME" type="string" /> + </class> + + <class name="TimestampedEntity" table="T_ENTITY"> + <id name="id" column="ID" type="string"> + <generator class="assigned"/> + </id> + <timestamp name="timestamp" column="TS" /> + <property name="name" column="NAME" type="string" /> + </class> + +</hibernate-mapping> + Added: branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/TimestampedEntity.java =================================================================== --- branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/TimestampedEntity.java 2006-07-21 16:52:30 UTC (rev 10131) +++ branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/TimestampedEntity.java 2006-07-21 16:53:54 UTC (rev 10132) @@ -0,0 +1,47 @@ +package org.hibernate.test.ops; + +import java.util.Date; + +/** + * todo: describe TimestampedEntity + * + * @author Steve Ebersole + */ +public class TimestampedEntity { + private String id; + private String name; + private Date timestamp; + + public TimestampedEntity() { + } + + public TimestampedEntity(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } +} + Added: branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/VersionedEntity.java =================================================================== --- branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/VersionedEntity.java 2006-07-21 16:52:30 UTC (rev 10131) +++ branches/Branch_3_2/Hibernate3/test/org/hibernate/test/ops/VersionedEntity.java 2006-07-21 16:53:54 UTC (rev 10132) @@ -0,0 +1,44 @@ +package org.hibernate.test.ops; + +/** + * todo: describe VersionedEntity + * + * @author Steve Ebersole + */ +public class VersionedEntity { + private String id; + private String name; + private long version; + + public VersionedEntity() { + } + + public VersionedEntity(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } +} |