FileDocCategorySizeDatePackage
JPALockTest.javaAPI DocHibernate 3.2.57531Tue May 15 15:15:58 BST 2007org.hibernate.test.jpa.lock

JPALockTest

public class JPALockTest extends org.hibernate.test.jpa.AbstractJPATest
Tests specifically relating to section 3.3.5.3 [Lock Modes] of the JPA persistence specification (as of the Proposed Final Draft).
author
Steve Ebersole

Fields Summary
Constructors Summary
public JPALockTest(String name)

		super( name );
	
Methods Summary
public static junit.framework.Testsuite()

		return new FunctionalTestClassTestSuite( JPALockTest.class );
	
public voidtestLockModeTypeRead()
Test the equivalent of EJB3 LockModeType.READ

From the spec:

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:

  • 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.
  • 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.

    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.

    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.

    Odd as it may sound, EJB3 LockModeType.READ actually maps to the Hibernate LockMode.UPGRADE

    		if ( ! readCommittedIsolationMaintained( "ejb3 lock tests" ) ) {
    			return;
    		}
    		if ( getDialect().doesReadCommittedCauseWritersToBlockReaders() ) {
    			reportSkip( "write locks block readers", "jpa read locking" );
    			return;
    		}
    
    		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();
    	
public voidtestLockModeTypeWrite()
Test the equivalent of EJB3 LockModeType.WRITE

From the spec:

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.

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.

Due to the requirement that LockModeType.WRITE needs to force a version increment, a new Hibernate LockMode was added to support this behavior: {@link org.hibernate.LockMode#FORCE}.

		if ( ! readCommittedIsolationMaintained( "ejb3 lock tests" ) ) {
			return;
		}
		if ( getDialect().doesReadCommittedCauseWritersToBlockReaders() ) {
			reportSkip( "write locks block readers", "jpa read locking" );
			return;
		}

		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 );
		MyEntity myEntity = new MyEntity();
		myEntity.setName( "Test" );
		s1.save( myEntity );
		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() );

		myEntity = (MyEntity) s1.get( MyEntity.class, myEntity.getId() );
		s1.lock( myEntity, LockMode.FORCE );
		assertTrue( "LockMode.FORCE on a unversioned entity should degrade nicely to UPGRADE", true );


		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 );
		s1.delete( myEntity );
		t1.commit();
		s1.close();