From: <ste...@us...> - 2006-02-14 03:24:26
|
Update of /cvsroot/hibernate/Hibernate3/test/org/hibernate/test/ejb3/lock In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv10165/test/org/hibernate/test/ejb3/lock Added Files: EJB3LockTest.java RepeatableReadTest.java Log Message: HHH-1416 & HHH-1421 : EJB3 LockModeTypes --- NEW FILE: EJB3LockTest.java --- package org.hibernate.test.ejb3.lock; import org.hibernate.test.TestCase; import org.hibernate.test.ejb3.Item; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.LockMode; import junit.framework.Test; import junit.framework.TestSuite; /** * Tests specifically relating to section 3.3.5.3 [Lock Modes] of the * EJB3 persistence specification (as of the <i>Proposed Final Draft</i>). * * @author Steve Ebersole */ public class EJB3LockTest extends TestCase { public EJB3LockTest(String name) { super( name ); } public static Test suite() { return new TestSuite( EJB3LockTest.class ); } protected String[] getMappings() { return new String[] { "ejb3/Item.hbm.xml", "ejb3/Part.hbm.xml" }; } public String getCacheConcurrencyStrategy() { // no second level caching return null; } protected void configure(Configuration cfg) { super.configure( cfg ); // error on Oracle 10g // cfg.setProperty( Environment.ISOLATION, "" + java.sql.Connection.TRANSACTION_READ_UNCOMMITTED ); cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); } /** * Test the equivalent of EJB3 LockModeType.READ * <p/> * From the spec: * <p/> * If transaction T1 calls lock(entity, LockModeType.READ) on a versioned object, the entity * manager must ensure that neither of the following phenomena can occur:<ul> * <li>P1 (Dirty read): Transaction T1 modifies a row. Another transaction T2 then reads that row and * obtains the modified value, before T1 has committed or rolled back. Transaction T2 eventually * commits successfully; it does not matter whether T1 commits or rolls back and whether it does * so before or after T2 commits. * <li>P2 (Non-repeatable read): Transaction T1 reads a row. Another transaction T2 then modifies or * deletes that row, before T1 has committed. Both transactions eventually commit successfully. * <p/> * This will generally be achieved by the entity manager acquiring a lock on the underlying database row. * Any such lock may be obtained immediately (so long as it is retained until commit completes), or the * lock may be deferred until commit time (although even then it must be retained until the commit completes). * Any implementation that supports repeatable reads in a way that prevents the above phenomena * is permissible. * <p/> * The persistence implementation is not required to support calling lock(entity, LockMode-Type.READ) * on a non-versioned object. When it cannot support such a lock call, it must throw the * PersistenceException. When supported, whether for versioned or non-versioned objects, LockMode-Type.READ * must always prevent the phenomena P1 and P2. Applications that call lock(entity, LockModeType.READ) * on non-versioned objects will not be portable. * <p/> * Odd as it may sound, EJB3 LockModeType.READ actually maps to the Hibernate LockMode.UPGRADE */ public void testLockModeTypeRead() { // todo : add some protections here for databases which have issues with concurrent access... final String initialName = "lock test"; // set up some test data Session s1 = getSessions().openSession(); Transaction t1 = s1.beginTransaction(); Item item = new Item(); item.setName( initialName ); s1.save( item ); t1.commit(); s1.close(); Long itemId = item.getId(); // perform the isolated update s1 = getSessions().openSession(); t1 = s1.beginTransaction(); item = ( Item ) s1.get( Item.class, itemId ); s1.lock( item, LockMode.UPGRADE ); item.setName( "updated" ); s1.flush(); Session s2 = getSessions().openSession(); Transaction t2 = s2.beginTransaction(); Item item2 = ( Item ) s2.get( Item.class, itemId ); assertEquals( "isolation not maintained", initialName, item2.getName() ); t1.commit(); s1.close(); item2 = ( Item ) s2.get( Item.class, itemId ); assertEquals( "repeatable read not maintained", initialName, item2.getName() ); t2.commit(); s2.close(); s1 = getSessions().openSession(); t1 = s1.beginTransaction(); s1.delete( item ); t1.commit(); s1.close(); } /** * Test the equivalent of EJB3 LockModeType.WRITE * <p/> * From the spec: * <p/> * If transaction T1 calls lock(entity, LockModeType.WRITE) on a versioned object, the entity * manager must avoid the phenomena P1 and P2 (as with LockModeType.READ) and must also force * an update (increment) to the entity's version column. A forced version update may be performed immediately, * or may be deferred until a flush or commit. If an entity is removed before a deferred version * update was to have been applied, the forced version update is omitted, since the underlying database * row no longer exists. * <p/> * The persistence implementation is not required to support calling lock(entity, LockMode-Type.WRITE) * on a non-versioned object. When it cannot support a such lock call, it must throw the * PersistenceException. When supported, whether for versioned or non-versioned objects, LockMode-Type.WRITE * must always prevent the phenomena P1 and P2. For non-versioned objects, whether or * not LockModeType.WRITE has any additional behaviour is vendor-specific. Applications that call * lock(entity, LockModeType.WRITE) on non-versioned objects will not be portable. * <p/> * Due to the requirement that LockModeType.WRITE needs to force a version increment, * a new Hibernate LockMode was added to support this behavior: {@link LockMode#FORCE}. */ public void testLockModeTypeWrite() { // todo : add some protections here for databases which have issues with concurrent access... final String initialName = "lock test"; // set up some test data Session s1 = getSessions().openSession(); Transaction t1 = s1.beginTransaction(); Item item = new Item(); item.setName( initialName ); s1.save( item ); t1.commit(); s1.close(); Long itemId = item.getId(); long initialVersion = item.getVersion(); s1 = getSessions().openSession(); t1 = s1.beginTransaction(); item = ( Item ) s1.get( Item.class, itemId ); s1.lock( item, LockMode.FORCE ); assertEquals( "no forced version increment", initialVersion + 1, item.getVersion() ); s1.lock( item, LockMode.FORCE ); assertEquals( "subsequent LockMode.FORCE did not no-op", initialVersion + 1, item.getVersion() ); Session s2 = getSessions().openSession(); Transaction t2 = s2.beginTransaction(); Item item2 = ( Item ) s2.get( Item.class, itemId ); assertEquals( "isolation not maintained", initialName, item2.getName() ); item.setName( "updated-1" ); s1.flush(); // currently an unfortunate side effect... assertEquals( initialVersion + 2, item.getVersion() ); t1.commit(); s1.close(); item2.setName( "updated" ); try { t2.commit(); fail( "optimisitc lock should have failed" ); } catch( Throwable ignore ) { // expected behavior t2.rollback(); } finally { s2.close(); } s1 = getSessions().openSession(); t1 = s1.beginTransaction(); s1.delete( item ); t1.commit(); s1.close(); } } --- NEW FILE: RepeatableReadTest.java --- package org.hibernate.test.ejb3.lock; import org.hibernate.test.TestCase; import org.hibernate.test.ejb3.Item; import org.hibernate.test.ejb3.Part; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.LockMode; import org.hibernate.StaleObjectStateException; import junit.framework.Test; import junit.framework.TestSuite; import java.math.BigDecimal; /** * Test that the Hibernate Session complies with REPEATABLE_READ isolation * semantics. * * @author Steve Ebersole */ public class RepeatableReadTest extends TestCase { public RepeatableReadTest(String name) { super( name ); } public static Test suite() { return new TestSuite( RepeatableReadTest.class ); } protected String[] getMappings() { return new String[] { "ejb3/Item.hbm.xml", "ejb3/Part.hbm.xml" }; } public String getCacheConcurrencyStrategy() { // no second level caching return null; } protected void configure(Configuration cfg) { super.configure( cfg ); // error on Oracle 10g // cfg.setProperty( Environment.ISOLATION, "" + java.sql.Connection.TRANSACTION_READ_UNCOMMITTED ); cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" ); } // versioned entity tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public void testStaleVersionedInstanceFoundInQueryResult() { String check = "EJB3 Specification"; Session s1 = getSessions().openSession(); Transaction t1 = s1.beginTransaction(); Item item = new Item( check ); s1.save( item ); t1.commit(); s1.close(); Long itemId = item.getId(); long initialVersion = item.getVersion(); // Now, open a new Session and re-load the item... s1 = getSessions().openSession(); t1 = s1.beginTransaction(); item = ( Item ) s1.get( Item.class, itemId ); // now that the item is associated with the persistence-context of that session, // open a new session and modify it "behind the back" of the first session Session s2 = getSessions().openSession(); Transaction t2 = s2.beginTransaction(); Item item2 = ( Item ) s2.get( Item.class, itemId ); item2.setName( "EJB3 Persistence Spec" ); t2.commit(); s2.close(); // at this point, s1 now contains stale data, so try an hql query which // returns said item and make sure we get the previously associated state // (i.e., the old name and the old version) item2 = ( Item ) s1.createQuery( "from Item" ).list().get( 0 ); assertTrue( item == item2 ); assertEquals( "encountered non-repeatable read", check, item2.getName() ); assertEquals( "encountered non-repeatable read", initialVersion, item2.getVersion() ); // clean up s1.refresh( item ); s1.delete( item ); t1.commit(); s1.close(); } public void testStaleVersionedInstanceFoundOnLock() { String check = "EJB3 Specification"; Session s1 = getSessions().openSession(); Transaction t1 = s1.beginTransaction(); Item item = new Item( check ); s1.save( item ); t1.commit(); s1.close(); Long itemId = item.getId(); long initialVersion = item.getVersion(); // Now, open a new Session and re-load the item... s1 = getSessions().openSession(); t1 = s1.beginTransaction(); item = ( Item ) s1.get( Item.class, itemId ); // now that the item is associated with the persistence-context of that session, // open a new session and modify it "behind the back" of the first session Session s2 = getSessions().openSession(); Transaction t2 = s2.beginTransaction(); Item item2 = ( Item ) s2.get( Item.class, itemId ); item2.setName( "EJB3 Persistence Spec" ); t2.commit(); s2.close(); // at this point, s1 now contains stale data, so acquire a READ lock // and make sure we get the already associated state (i.e., the old // name and the old version) s1.lock( item, LockMode.READ ); item2 = ( Item ) s1.get( Item.class, itemId ); assertTrue( item == item2 ); assertEquals( "encountered non-repeatable read", check, item2.getName() ); assertEquals( "encountered non-repeatable read", initialVersion, item2.getVersion() ); // attempt to acquire an UPGRADE lock; this should fail try { s1.lock( item, LockMode.UPGRADE ); fail( "expected UPGRADE lock failure" ); } catch( StaleObjectStateException expected ) { // this is the expected behavior } // clean up s1.refresh( item ); s1.delete( item ); t1.commit(); s1.close(); } // non-versioned entity tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public void testStaleNonVersionedInstanceFoundInQueryResult() { String check = "Lock Modes"; Session s1 = getSessions().openSession(); Transaction t1 = s1.beginTransaction(); Part part = new Part( new Item( "EJB3 Specification" ), check, "3.3.5.3", new BigDecimal( 0.0 ) ); s1.save( part ); t1.commit(); s1.close(); Long partId = part.getId(); // Now, open a new Session and re-load the part... s1 = getSessions().openSession(); t1 = s1.beginTransaction(); part = ( Part ) s1.get( Part.class, partId ); // now that the item is associated with the persistence-context of that session, // open a new session and modify it "behind the back" of the first session Session s2 = getSessions().openSession(); Transaction t2 = s2.beginTransaction(); Part part2 = ( Part ) s2.get( Part.class, partId ); part2.setName( "Lock Mode Types" ); t2.commit(); s2.close(); // at this point, s1 now contains stale data, so try an hql query which // returns said part and make sure we get the previously associated state // (i.e., the old name) part2 = ( Part ) s1.createQuery( "from Part" ).list().get( 0 ); assertTrue( part == part2 ); assertEquals( "encountered non-repeatable read", check, part2.getName() ); // clean up s1.delete( part ); t1.commit(); s1.close(); } public void testStaleNonVersionedInstanceFoundOnLock() { String check = "Lock Modes"; Session s1 = getSessions().openSession(); Transaction t1 = s1.beginTransaction(); Part part = new Part( new Item( "EJB3 Specification" ), check, "3.3.5.3", new BigDecimal( 0.0 ) ); s1.save( part ); t1.commit(); s1.close(); Long partId = part.getId(); // Now, open a new Session and re-load the part... s1 = getSessions().openSession(); t1 = s1.beginTransaction(); part = ( Part ) s1.get( Part.class, partId ); // now that the item is associated with the persistence-context of that session, // open a new session and modify it "behind the back" of the first session Session s2 = getSessions().openSession(); Transaction t2 = s2.beginTransaction(); Part part2 = ( Part ) s2.get( Part.class, partId ); part2.setName( "Lock Mode Types" ); t2.commit(); s2.close(); // at this point, s1 now contains stale data, so acquire a READ lock // and make sure we get the already associated state (i.e., the old // name and the old version) s1.lock( part, LockMode.READ ); part2 = ( Part ) s1.get( Part.class, partId ); assertTrue( part == part2 ); assertEquals( "encountered non-repeatable read", check, part2.getName() ); // then acquire an UPGRADE lock; this should fail s1.lock( part, LockMode.UPGRADE ); part2 = ( Part ) s1.get( Part.class, partId ); assertTrue( part == part2 ); assertEquals( "encountered non-repeatable read", check, part2.getName() ); // clean up // s1.refresh( item ); s1.delete( part ); t1.commit(); s1.close(); } } |