From: <hib...@li...> - 2006-08-05 04:28:00
|
Author: ste...@jb... Date: 2006-08-05 00:27:55 -0400 (Sat, 05 Aug 2006) New Revision: 10226 Modified: trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java trunk/Hibernate3/src/org/hibernate/sql/Delete.java trunk/Hibernate3/test/org/hibernate/test/optlock/Document.hbm.xml trunk/Hibernate3/test/org/hibernate/test/optlock/OptimisticLockTest.java Log: HHH-1677 : optimistic-lock and delete Modified: trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java 2006-08-04 20:33:48 UTC (rev 10225) +++ trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java 2006-08-05 04:27:55 UTC (rev 10226) @@ -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; @@ -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 @@ -2374,7 +2375,8 @@ final int j, final Object object, final String sql, - final SessionImplementor session) throws HibernateException { + 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() ); Modified: trunk/Hibernate3/src/org/hibernate/sql/Delete.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/sql/Delete.java 2006-08-04 20:33:48 UTC (rev 10225) +++ trunk/Hibernate3/src/org/hibernate/sql/Delete.java 2006-08-05 04:27:55 UTC (rev 10226) @@ -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: trunk/Hibernate3/test/org/hibernate/test/optlock/Document.hbm.xml =================================================================== --- trunk/Hibernate3/test/org/hibernate/test/optlock/Document.hbm.xml 2006-08-04 20:33:48 UTC (rev 10225) +++ trunk/Hibernate3/test/org/hibernate/test/optlock/Document.hbm.xml 2006-08-05 04:27:55 UTC (rev 10226) @@ -13,11 +13,11 @@ <hibernate-mapping package="org.hibernate.test.optlock"> - <class name="Document" - entity-name="Dirty" - table="Document" - optimistic-lock="dirty" - dynamic-update="true"> + <class name="Document" + entity-name="Dirty" + table="Document" + optimistic-lock="dirty" + dynamic-update="true"> <id name="id"> <generator class="native"/> </id> @@ -32,11 +32,11 @@ <property name="text" column="`text`"/> </class> - <class name="Document" - entity-name="All" - table="Document" - optimistic-lock="all" - dynamic-update="true"> + <class name="Document" + entity-name="All" + table="Document" + optimistic-lock="all" + dynamic-update="true"> <id name="id"> <generator class="native"/> </id> Modified: trunk/Hibernate3/test/org/hibernate/test/optlock/OptimisticLockTest.java =================================================================== --- trunk/Hibernate3/test/org/hibernate/test/optlock/OptimisticLockTest.java 2006-08-04 20:33:48 UTC (rev 10225) +++ trunk/Hibernate3/test/org/hibernate/test/optlock/OptimisticLockTest.java 2006-08-05 04:27:55 UTC (rev 10226) @@ -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); - } - } |