Package org.hibernate.test.jpa.lock

Source Code of org.hibernate.test.jpa.lock.JPALockTest

package org.hibernate.test.jpa.lock;

import junit.framework.Test;

import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.testing.junit.functional.FunctionalTestClassTestSuite;
import org.hibernate.test.jpa.AbstractJPATest;
import org.hibernate.test.jpa.Item;
import org.hibernate.test.jpa.MyEntity;
import org.hibernate.util.ReflectHelper;

/**
* Tests specifically relating to section 3.3.5.3 [Lock Modes] of the
* JPA persistence specification (as of the <i>Proposed Final Draft</i>).
*
* @author Steve Ebersole
*/
public class JPALockTest extends AbstractJPATest {
  public JPALockTest(String name) {
    super( name );
  }

  public static Test suite() {
    return new FunctionalTestClassTestSuite( JPALockTest.class );
  }

  /**
   * 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/>
   * EJB3 LockModeType.READ actually maps to the Hibernate LockMode.OPTIMISTIC
   */
  public void testLockModeTypeRead() {
    if ( !readCommittedIsolationMaintained( "ejb3 lock tests" ) ) {
      return;
    }
    if ( getDialect().doesReadCommittedCauseWritersToBlockReaders() ) {
      reportSkip( "deadlock", "jpa read locking" );
      return;
    }
    if ( getDialect() instanceof HSQLDialect ) {
      int hsqldbVersion = 18;
      try {
        Class props = ReflectHelper
            .classForName( "org.hsqldb.persist.HsqlDatabaseProperties" );
        String versionString = (String) props.getDeclaredField(
            "THIS_VERSION").get(null);

        hsqldbVersion = Integer.parseInt( versionString.substring( 0, 1 ) ) * 10;
        hsqldbVersion += Integer
            .parseInt( versionString.substring( 2, 3 ) );
      } catch ( Throwable e ) {
        // must be a very old version
      }
      if ( hsqldbVersion < 20 )
        reportSkip( "hsqldb 1.8.x only supports read uncommitted", "lock doesn't work on hsqldb 1.8.x");
      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();

    // do 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 org.hibernate.LockMode#FORCE}.
   */
  public void testLockModeTypeWrite() {
    if ( !readCommittedIsolationMaintained( "ejb3 lock tests" ) ) {
      return;
    }
    if ( getDialect().doesReadCommittedCauseWritersToBlockReaders() ) {
      reportSkip( "deadlock", "jpa write 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 un-versioned 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( "optimistic 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();
  }
}
TOP

Related Classes of org.hibernate.test.jpa.lock.JPALockTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.