From: <hib...@li...> - 2006-08-05 04:28:26
|
Author: ste...@jb... Date: 2006-08-05 00:28:22 -0400 (Sat, 05 Aug 2006) New Revision: 10227 Modified: branches/Branch_3_2/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java branches/Branch_3_2/Hibernate3/src/org/hibernate/sql/Delete.java branches/Branch_3_2/Hibernate3/test/org/hibernate/test/optlock/Document.hbm.xml branches/Branch_3_2/Hibernate3/test/org/hibernate/test/optlock/OptimisticLockTest.java Log: HHH-1677 : optimistic-lock and delete Modified: branches/Branch_3_2/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java =================================================================== --- branches/Branch_3_2/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java 2006-08-05 04:27:55 UTC (rev 10226) +++ branches/Branch_3_2/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java 2006-08-05 04:28:22 UTC (rev 10227) @@ -43,6 +43,7 @@ import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.Versioning; import org.hibernate.engine.ExecuteUpdateResultCheckStyle; +import org.hibernate.engine.EntityKey; import org.hibernate.exception.JDBCExceptionHelper; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.PostInsertIdentifierGenerator; @@ -1269,7 +1270,7 @@ lockers.put( LockMode.READ, generateLocker( LockMode.READ ) ); lockers.put( LockMode.UPGRADE, generateLocker( LockMode.UPGRADE ) ); lockers.put( LockMode.UPGRADE_NOWAIT, generateLocker( LockMode.UPGRADE_NOWAIT ) ); - lockers.put( LockMode.FORCE, generateLocker( LockMode.FORCE ) ); + //lockers.put( LockMode.FORCE, generateLocker( LockMode.FORCE ) ); } protected LockingStrategy generateLocker(LockMode lockMode) { @@ -1757,9 +1758,9 @@ getPropertyUpdateability() : //optimistic-lock="all", include all updatable properties includeProperty; //optimistic-lock="dirty", include all properties we are updating this time + boolean[] versionability = getPropertyVersionability(); + Type[] types = getPropertyTypes(); for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) { - boolean[] versionability = getPropertyVersionability(); - Type[] types = getPropertyTypes(); boolean include = includeInWhere[i] && isPropertyOfTable( i, j ) && versionability[i]; @@ -2233,7 +2234,7 @@ else if ( isNullableTable( j ) && isAllNull( fields, j ) ) { //if all fields are null, we might need to delete existing row isRowToUpdate = true; - delete( id, oldVersion, j, object, getSQLDeleteStrings()[j], session ); + delete( id, oldVersion, j, object, getSQLDeleteStrings()[j], session, null ); } else { //there is probably a row there, so try to update @@ -2370,11 +2371,12 @@ */ protected void delete( final Serializable id, - final Object version, - final int j, - final Object object, - final String sql, - final SessionImplementor session) throws HibernateException { + final Object version, + final int j, + final Object object, + final String sql, + final SessionImplementor session, + final Object[] loadedState) throws HibernateException { if ( isInverseTable( j ) ) { return; @@ -2428,12 +2430,26 @@ // Do the key. The key is immutable so we can use the _current_ object state - not necessarily // the state at the time the delete was issued getIdentifierType().nullSafeSet( delete, id, index, session ); + index += getIdentifierColumnSpan(); // We should use the _current_ object state (ie. after any updates that occurred during flush) if ( useVersion ) { - getVersionType().nullSafeSet( delete, version, getIdentifierColumnSpan() + index, session ); + getVersionType().nullSafeSet( delete, version, index, session ); } + else if ( entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION && loadedState != null ) { + boolean[] versionability = getPropertyVersionability(); + Type[] types = getPropertyTypes(); + for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) { + if ( isPropertyOfTable( i, j ) && versionability[i] ) { + // this property belongs to the table and it is not specifically + // excluded from optimistic locking by optimistic-lock="false" + boolean[] settable = types[i].toColumnNullness( loadedState[i], getFactory() ); + types[i].nullSafeSet( delete, loadedState[i], index, settable, session ); + index += ArrayHelper.countTrue( settable ); + } + } + } if ( useBatch ) { session.getBatcher().addToBatch( expectation ); @@ -2586,14 +2602,72 @@ */ public void delete(Serializable id, Object version, Object object, SessionImplementor session) throws HibernateException { + final int span = getTableSpan(); + boolean isImpliedOptimisticLocking = entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION; + Object[] loadedState = null; + if ( isImpliedOptimisticLocking ) { + // need to treat this as if it where optimistic-lock="all" (dirty does *not* make sense); + // first we need to locate the "loaded" state + // + // Note, it potentially could be a proxy, so perform the location the safe way... + EntityKey key = new EntityKey( id, this, session.getEntityMode() ); + Object entity = session.getPersistenceContext().getEntity( key ); + if ( entity != null ) { + EntityEntry entry = session.getPersistenceContext().getEntry( entity ); + loadedState = entry.getLoadedState(); + } + } - final int span = getTableSpan(); + final String[] deleteStrings; + if ( isImpliedOptimisticLocking && loadedState != null ) { + // we need to utilize dynamic delete statements + deleteStrings = generateSQLDeletStrings( loadedState ); + } + else { + // otherwise, utilize the static delete statements + deleteStrings = getSQLDeleteStrings(); + } + for ( int j = span - 1; j >= 0; j-- ) { - delete( id, version, j, object, getSQLDeleteStrings()[j], session ); + delete( id, version, j, object, deleteStrings[j], session, loadedState ); } } + private String[] generateSQLDeletStrings(Object[] loadedState) { + int span = getTableSpan(); + String[] deleteStrings = new String[span]; + for ( int j = span - 1; j >= 0; j-- ) { + Delete delete = new Delete() + .setTableName( getTableName( j ) ) + .setPrimaryKeyColumnNames( getKeyColumns( j ) ); + if ( getFactory().getSettings().isCommentsEnabled() ) { + delete.setComment( "delete " + getEntityName() + " [" + j + "]" ); + } + + boolean[] versionability = getPropertyVersionability(); + Type[] types = getPropertyTypes(); + for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) { + if ( isPropertyOfTable( i, j ) && versionability[i] ) { + // this property belongs to the table and it is not specifically + // excluded from optimistic locking by optimistic-lock="false" + String[] propertyColumnNames = getPropertyColumnNames( i ); + boolean[] propertyNullness = types[i].toColumnNullness( loadedState[i], getFactory() ); + for ( int k = 0; k < propertyNullness.length; k++ ) { + if ( propertyNullness[k] ) { + delete.addWhereFragment( propertyColumnNames[k] + " = ?" ); + } + else { + delete.addWhereFragment( propertyColumnNames[k] + " is null" ); + } + } + } + } + deleteStrings[j] = delete.toStatementString(); + } + return deleteStrings; + } + protected void logStaticSQL() { if ( log.isDebugEnabled() ) { log.debug( "Static SQL for entity: " + getEntityName() ); @@ -3596,11 +3670,11 @@ public Type getIdentifierType() { return entityMetamodel.getIdentifierProperty().getType(); } - + public boolean hasSubselectLoadableCollections() { return hasSubselectLoadableCollections; } - + public int[] getNaturalIdentifierProperties() { return entityMetamodel.getNaturalIdentifierProperties(); } @@ -3691,7 +3765,7 @@ public boolean hasNaturalIdentifier() { return entityMetamodel.hasNaturalIdentifier(); } - + public void setPropertyValue(Object object, String propertyName, Object value, EntityMode entityMode) throws HibernateException { getTuplizer( entityMode ).setPropertyValue( object, propertyName, value ); Modified: branches/Branch_3_2/Hibernate3/src/org/hibernate/sql/Delete.java =================================================================== --- branches/Branch_3_2/Hibernate3/src/org/hibernate/sql/Delete.java 2006-08-05 04:27:55 UTC (rev 10226) +++ branches/Branch_3_2/Hibernate3/src/org/hibernate/sql/Delete.java 2006-08-05 04:28:22 UTC (rev 10227) @@ -61,6 +61,16 @@ return this; } + public Delete addWhereFragment(String fragment) { + if ( where == null ) { + where = fragment; + } + else { + where += ( " and " + fragment ); + } + return this; + } + public Delete setPrimaryKeyColumnNames(String[] primaryKeyColumnNames) { this.primaryKeyColumnNames = primaryKeyColumnNames; return this; Modified: branches/Branch_3_2/Hibernate3/test/org/hibernate/test/optlock/Document.hbm.xml =================================================================== --- branches/Branch_3_2/Hibernate3/test/org/hibernate/test/optlock/Document.hbm.xml 2006-08-05 04:27:55 UTC (rev 10226) +++ branches/Branch_3_2/Hibernate3/test/org/hibernate/test/optlock/Document.hbm.xml 2006-08-05 04:28:22 UTC (rev 10227) @@ -1,7 +1,7 @@ <?xml version="1.0"?> -<!DOCTYPE hibernate-mapping PUBLIC - "-//Hibernate/Hibernate Mapping DTD 3.0//EN" - "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> +<!DOCTYPE hibernate-mapping PUBLIC + "-//Hibernate/Hibernate Mapping DTD 3.0//EN" + "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- @@ -12,44 +12,44 @@ --> <hibernate-mapping package="org.hibernate.test.optlock"> - - <class name="Document" - entity-name="Dirty" - table="Document" - optimistic-lock="dirty" - dynamic-update="true"> - <id name="id"> - <generator class="native"/> - </id> - <property name="title"/> - <property name="author"/> - <component name="pubDate"> - <property name="year" not-null="true"/> - <property name="month"/> - </component> - <property name="summary"/> - <property name="totalSales" optimistic-lock="false"/> - <property name="text" column="`text`"/> - </class> - - <class name="Document" - entity-name="All" - table="Document" - optimistic-lock="all" - dynamic-update="true"> - <id name="id"> - <generator class="native"/> - </id> - <property name="title"/> - <property name="author"/> - <component name="pubDate"> - <property name="year" not-null="true"/> - <property name="month"/> - </component> - <property name="summary"/> - <property name="totalSales" optimistic-lock="false"/> - <property name="text" column="`text`"/> - </class> - + + <class name="Document" + entity-name="Dirty" + table="Document" + optimistic-lock="dirty" + dynamic-update="true"> + <id name="id"> + <generator class="native"/> + </id> + <property name="title"/> + <property name="author"/> + <component name="pubDate"> + <property name="year" not-null="true"/> + <property name="month"/> + </component> + <property name="summary"/> + <property name="totalSales" optimistic-lock="false"/> + <property name="text" column="`text`"/> + </class> + + <class name="Document" + entity-name="All" + table="Document" + optimistic-lock="all" + dynamic-update="true"> + <id name="id"> + <generator class="native"/> + </id> + <property name="title"/> + <property name="author"/> + <component name="pubDate"> + <property name="year" not-null="true"/> + <property name="month"/> + </component> + <property name="summary"/> + <property name="totalSales" optimistic-lock="false"/> + <property name="text" column="`text`"/> + </class> + </hibernate-mapping> Modified: branches/Branch_3_2/Hibernate3/test/org/hibernate/test/optlock/OptimisticLockTest.java =================================================================== --- branches/Branch_3_2/Hibernate3/test/org/hibernate/test/optlock/OptimisticLockTest.java 2006-08-05 04:27:55 UTC (rev 10226) +++ branches/Branch_3_2/Hibernate3/test/org/hibernate/test/optlock/OptimisticLockTest.java 2006-08-05 04:28:22 UTC (rev 10227) @@ -10,146 +10,134 @@ import org.hibernate.test.TestCase; /** + * Tests relating to the optimisitc-lock mapping option. + * * @author Gavin King + * @author Steve Ebersole */ public class OptimisticLockTest extends TestCase { - + public OptimisticLockTest(String str) { super(str); } - + + protected String[] getMappings() { + return new String[] { "optlock/Document.hbm.xml" }; + } + + public static Test suite() { + return new TestSuite(OptimisticLockTest.class); + } + public void testOptimisticLockDirty() { - Session s = openSession(); - Transaction t = s.beginTransaction(); - Document doc = new Document(); - doc.setTitle("Hibernate in Action"); - doc.setAuthor("Bauer et al"); - doc.setSummary("Very boring book about persistence"); - doc.setText("blah blah yada yada yada"); - doc.setPubDate( new PublicationDate(2004) ); - s.save("Dirty", doc); - s.flush(); - doc.setSummary("A modern classic"); - s.flush(); - doc.getPubDate().setMonth( new Integer(3) ); - s.flush(); - s.delete(doc); - t.commit(); - s.close(); + testUpdateOptimisticLockFailure( "Dirty" ); } public void testOptimisticLockAll() { - Session s = openSession(); - Transaction t = s.beginTransaction(); - Document doc = new Document(); - doc.setTitle("Hibernate in Action"); - doc.setAuthor("Bauer et al"); - doc.setSummary("Very boring book about persistence"); - doc.setText("blah blah yada yada yada"); - doc.setPubDate( new PublicationDate(2004) ); - s.save("All", doc); - s.flush(); - doc.setSummary("A modern classic"); - s.flush(); - doc.getPubDate().setMonth( new Integer(3) ); - s.flush(); - s.delete(doc); - t.commit(); - s.close(); + testUpdateOptimisticLockFailure( "All" ); } - //TODO: also test that non-overlapping changes behavior. - public void testOptimisticLockDirtyDeleteFailureExpected() { - //HHH-1677 + public void testOptimisticLockDirtyDelete() { + testDeleteOptimisticLockFailure( "Dirty" ); + } + public void testOptimisticLockAllDelete() { + testDeleteOptimisticLockFailure( "All" ); + } + + private void testUpdateOptimisticLockFailure(String entityName) { Session s = openSession(); Transaction t = s.beginTransaction(); Document doc = new Document(); - doc.setTitle("Hibernate in Action"); - doc.setAuthor("Bauer et al"); - doc.setSummary("Very boring book about persistence"); - doc.setText("blah blah yada yada yada"); - doc.setPubDate( new PublicationDate(2004) ); - s.save("Dirty", doc); - s.flush(); - doc.setSummary("A modern classic"); - s.flush(); - doc.getPubDate().setMonth( new Integer(3) ); - s.flush(); + doc.setTitle( "Hibernate in Action" ); + doc.setAuthor( "Bauer et al" ); + doc.setSummary( "Very boring book about persistence" ); + doc.setText( "blah blah yada yada yada" ); + doc.setPubDate( new PublicationDate( 2004 ) ); + s.save( entityName, doc ); t.commit(); s.close(); - + s = openSession(); t = s.beginTransaction(); - doc = (Document) s.get("Dirty", doc.getId()); - - Session other = openSession(); - Transaction othert = other.beginTransaction(); - Document otherDoc = (Document) other.get("Dirty", doc.getId()); - otherDoc.setSummary( "my other summary" ); - other.flush(); - othert.commit(); - other.close(); - + doc = ( Document ) s.get( entityName, doc.getId() ); + + Session otherSession = openSession(); + otherSession.beginTransaction(); + Document otherDoc = ( Document ) otherSession.get( entityName, doc.getId() ); + otherDoc.setSummary( "A modern classic" ); + otherSession.getTransaction().commit(); + otherSession.close(); + try { - s.delete(doc); - t.commit(); - fail("Should fail since other session have update the summary"); - } catch(StaleObjectStateException soe) { - // expected + doc.setSummary( "A machiavelian achievement of epic proportions" ); + s.flush(); + fail( "expecting opt lock failure" ); } + catch ( StaleObjectStateException expected ) { + // expected result... + } + s.clear(); + t.commit(); s.close(); + + s = openSession(); + t = s.beginTransaction(); + doc = ( Document ) s.load( entityName, doc.getId() ); + s.delete( entityName, doc ); + t.commit(); + s.close(); } - public void testOptimisticLockAllDeleteFailureExpected() { - //HHH-1677 + private void testDeleteOptimisticLockFailure(String entityName) { Session s = openSession(); Transaction t = s.beginTransaction(); Document doc = new Document(); - doc.setTitle("Hibernate in Action"); - doc.setAuthor("Bauer et al"); - doc.setSummary("Very boring book about persistence"); - doc.setText("blah blah yada yada yada"); - doc.setPubDate( new PublicationDate(2004) ); - s.save("Dirty", doc); + doc.setTitle( "Hibernate in Action" ); + doc.setAuthor( "Bauer et al" ); + doc.setSummary( "Very boring book about persistence" ); + doc.setText( "blah blah yada yada yada" ); + doc.setPubDate( new PublicationDate( 2004 ) ); + s.save( entityName, doc ); s.flush(); - doc.setSummary("A modern classic"); + doc.setSummary( "A modern classic" ); s.flush(); - doc.getPubDate().setMonth( new Integer(3) ); + doc.getPubDate().setMonth( new Integer( 3 ) ); s.flush(); t.commit(); s.close(); s = openSession(); t = s.beginTransaction(); - doc = (Document) s.get("All", doc.getId()); + doc = ( Document ) s.get( entityName, doc.getId() ); Session other = openSession(); Transaction othert = other.beginTransaction(); - Document otherDoc = (Document) other.get("All", doc.getId()); + Document otherDoc = ( Document ) other.get( entityName, doc.getId() ); otherDoc.setSummary( "my other summary" ); other.flush(); othert.commit(); other.close(); try { - s.delete(doc); - t.commit(); - fail("Should fail since other session have update the summary"); - } catch(StaleObjectStateException soe) { + s.delete( doc ); + s.flush(); + fail( "expecting opt lock failure" ); + } + catch ( StaleObjectStateException e ) { // expected } - s.close(); - } + s.clear(); + t.commit(); + s.close(); - - protected String[] getMappings() { - return new String[] { "optlock/Document.hbm.xml" }; + s = openSession(); + t = s.beginTransaction(); + doc = ( Document ) s.load( entityName, doc.getId() ); + s.delete( entityName, doc ); + t.commit(); + s.close(); } - public static Test suite() { - return new TestSuite(OptimisticLockTest.class); - } - } |