JPALockTestpublic 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). |
Constructors Summary |
---|
public JPALockTest(String name)
super( name );
|
Methods Summary |
---|
public static junit.framework.Test | suite()
return new FunctionalTestClassTestSuite( JPALockTest.class );
| public void | testLockModeTypeRead()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 void | testLockModeTypeWrite()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();
|
|