[hibernate-commits] Hibernate SVN: r18688 - in core/trunk: core/src/main/java/org/hibernate/dialect/lock and 3 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Wed Feb 3 19:07:32 EST 2010


Author: smarlow at redhat.com
Date: 2010-02-03 19:07:31 -0500 (Wed, 03 Feb 2010)
New Revision: 18688

Modified:
   core/trunk/core/src/main/java/org/hibernate/dialect/Oracle9iDialect.java
   core/trunk/core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java
   core/trunk/core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java
   core/trunk/core/src/main/java/org/hibernate/event/def/AbstractLockUpgradeEventListener.java
   core/trunk/core/src/main/java/org/hibernate/sql/SimpleSelect.java
   core/trunk/entitymanager/src/test/java/org/hibernate/ejb/test/lock/LockTest.java
Log:
HHH-4765 Enhance Dialect support for JPA-2 locking.  pessimistic no wait/timed locking and additional pessimistic locking tests.  Oracle support for pessimistic locking

Modified: core/trunk/core/src/main/java/org/hibernate/dialect/Oracle9iDialect.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/dialect/Oracle9iDialect.java	2010-02-03 21:47:31 UTC (rev 18687)
+++ core/trunk/core/src/main/java/org/hibernate/dialect/Oracle9iDialect.java	2010-02-04 00:07:31 UTC (rev 18688)
@@ -26,6 +26,7 @@
 
 import java.sql.Types;
 
+import org.hibernate.LockOptions;
 import org.hibernate.sql.CaseFragment;
 import org.hibernate.sql.ANSICaseFragment;
 
@@ -58,9 +59,13 @@
 
 	public String getLimitString(String sql, boolean hasOffset) {
 		sql = sql.trim();
+		String forUpdateClause = null;
 		boolean isForUpdate = false;
-		if ( sql.toLowerCase().endsWith(" for update") ) {
-			sql = sql.substring( 0, sql.length()-11 );
+		final int forUpdateIndex = sql.toLowerCase().lastIndexOf( "for update") ;
+		if ( forUpdateIndex > -1 ) {
+			// save 'for update ...' and then remove it
+			forUpdateClause = sql.substring( forUpdateIndex );
+			sql = sql.substring( 0, forUpdateIndex-1 );
 			isForUpdate = true;
 		}
 
@@ -80,7 +85,8 @@
 		}
 
 		if ( isForUpdate ) {
-			pagingSelect.append( " for update" );
+			pagingSelect.append( " " );
+			pagingSelect.append( forUpdateClause );
 		}
 
 		return pagingSelect.toString();
@@ -98,4 +104,28 @@
 		// the standard SQL function name is current_timestamp...
 		return "current_timestamp";
 	}
+
+	// locking support
+	public String getForUpdateString() {
+		return " for update";
+	}
+
+	public String getWriteLockString(int timeout) {
+		if ( timeout == LockOptions.NO_WAIT ) {
+			return " for update nowait";
+		}
+		else if ( timeout > 0 ) {
+			// convert from milliseconds to seconds
+			float seconds = timeout / 1000.0f;
+			timeout = Math.round(seconds);
+			return " for update wait " + timeout;
+		}
+		else
+			return " for update";
+	}
+
+	public String getReadLockString(int timeout) {
+		return getWriteLockString( timeout );
+	}
+	
 }

Modified: core/trunk/core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java	2010-02-03 21:47:31 UTC (rev 18687)
+++ core/trunk/core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java	2010-02-04 00:07:31 UTC (rev 18688)
@@ -24,6 +24,7 @@
  */
 package org.hibernate.dialect.lock;
 
+import org.hibernate.LockOptions;
 import org.hibernate.persister.entity.Lockable;
 import org.hibernate.engine.SessionImplementor;
 import org.hibernate.engine.SessionFactoryImplementor;
@@ -72,7 +73,7 @@
 	public PessimisticReadSelectLockingStrategy(Lockable lockable, LockMode lockMode) {
 		this.lockable = lockable;
 		this.lockMode = lockMode;
-		this.sql = generateLockString();
+		this.sql = generateLockString(LockOptions.WAIT_FOREVER);
 	}
 
    /**
@@ -83,6 +84,13 @@
       Object version,
       Object object,
       int timeout, SessionImplementor session) throws StaleObjectStateException, JDBCException {
+		String sql = this.sql;
+		if ( timeout == LockOptions.NO_WAIT ) {
+			sql = generateLockString( LockOptions.NO_WAIT );
+		}
+		else if ( timeout > 0) {
+			sql = generateLockString( timeout );
+		}
 
 		SessionFactoryImplementor factory = session.getFactory();
 		try {
@@ -132,10 +140,12 @@
 		return lockMode;
 	}
 
-	protected String generateLockString() {
+	protected String generateLockString(int lockTimeout) {
 		SessionFactoryImplementor factory = lockable.getFactory();
+		LockOptions lockOptions = new LockOptions(this.lockMode);
+		lockOptions.setTimeOut( lockTimeout );
 		SimpleSelect select = new SimpleSelect( factory.getDialect() )
-				.setLockMode( lockMode )
+				.setLockOptions( lockOptions )
 				.setTableName( lockable.getRootTableName() )
 				.addColumn( lockable.getRootTableIdentifierColumnNames()[0] )
 				.addCondition( lockable.getRootTableIdentifierColumnNames(), "=?" );

Modified: core/trunk/core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java	2010-02-03 21:47:31 UTC (rev 18687)
+++ core/trunk/core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteSelectLockingStrategy.java	2010-02-04 00:07:31 UTC (rev 18688)
@@ -24,6 +24,7 @@
  */
 package org.hibernate.dialect.lock;
 
+import org.hibernate.LockOptions;
 import org.hibernate.persister.entity.Lockable;
 import org.hibernate.engine.SessionImplementor;
 import org.hibernate.engine.SessionFactoryImplementor;
@@ -72,7 +73,7 @@
 	public PessimisticWriteSelectLockingStrategy(Lockable lockable, LockMode lockMode) {
 		this.lockable = lockable;
 		this.lockMode = lockMode;
-		this.sql = generateLockString();
+		this.sql = generateLockString(LockOptions.WAIT_FOREVER);
 	}
 
    /**
@@ -83,6 +84,13 @@
       Object version,
       Object object,
       int timeout, SessionImplementor session) throws StaleObjectStateException, JDBCException {
+		String sql = this.sql;
+		if ( timeout == LockOptions.NO_WAIT ) {
+			sql = generateLockString( LockOptions.NO_WAIT );
+		}
+		else if ( timeout > 0) {
+			sql = generateLockString( timeout );
+		}
 
 		SessionFactoryImplementor factory = session.getFactory();
 		try {
@@ -132,10 +140,12 @@
 		return lockMode;
 	}
 
-	protected String generateLockString() {
+	protected String generateLockString(int lockTimeout) {
 		SessionFactoryImplementor factory = lockable.getFactory();
+		LockOptions lockOptions = new LockOptions(this.lockMode);
+		lockOptions.setTimeOut( lockTimeout );
 		SimpleSelect select = new SimpleSelect( factory.getDialect() )
-				.setLockMode( lockMode )
+				.setLockOptions( lockOptions )
 				.setTableName( lockable.getRootTableName() )
 				.addColumn( lockable.getRootTableIdentifierColumnNames()[0] )
 				.addCondition( lockable.getRootTableIdentifierColumnNames(), "=?" );

Modified: core/trunk/core/src/main/java/org/hibernate/event/def/AbstractLockUpgradeEventListener.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/event/def/AbstractLockUpgradeEventListener.java	2010-02-03 21:47:31 UTC (rev 18687)
+++ core/trunk/core/src/main/java/org/hibernate/event/def/AbstractLockUpgradeEventListener.java	2010-02-04 00:07:31 UTC (rev 18688)
@@ -107,7 +107,7 @@
 					entry.forceLocked( object, nextVersion );
 				}
 				else {
-					persister.lock( entry.getId(), entry.getVersion(), object, requestedLockMode, source );
+					persister.lock( entry.getId(), entry.getVersion(), object, lockOptions, source );
 				}
 				entry.setLockMode(requestedLockMode);
 			}

Modified: core/trunk/core/src/main/java/org/hibernate/sql/SimpleSelect.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/sql/SimpleSelect.java	2010-02-03 21:47:31 UTC (rev 18687)
+++ core/trunk/core/src/main/java/org/hibernate/sql/SimpleSelect.java	2010-02-04 00:07:31 UTC (rev 18688)
@@ -33,6 +33,7 @@
 import java.util.Set;
 
 import org.hibernate.LockMode;
+import org.hibernate.LockOptions;
 import org.hibernate.dialect.Dialect;
 
 /**
@@ -51,7 +52,7 @@
 	private String tableName;
 	private String orderBy;
 	private Dialect dialect;
-	private LockMode lockMode = LockMode.READ;
+	private LockOptions lockOptions = new LockOptions( LockMode.READ);
 	private String comment;
 
 	private List columns = new ArrayList();
@@ -99,8 +100,13 @@
 		return this;
 	}
 
+	public SimpleSelect setLockOptions( LockOptions lockOptions ) {
+	   LockOptions.copy(lockOptions, this.lockOptions);
+		return this;
+	}
+
 	public SimpleSelect setLockMode(LockMode lockMode) {
-		this.lockMode = lockMode;
+		this.lockOptions.setLockMode( lockMode );
 		return this;
 	}
 
@@ -172,7 +178,7 @@
 		}
 		
 		buf.append(" from ")
-			.append( dialect.appendLockHint(lockMode, tableName) );
+			.append( dialect.appendLockHint(lockOptions.getLockMode(), tableName) );
 		
 		if ( whereTokens.size() > 0 ) {
 			buf.append(" where ")
@@ -181,8 +187,8 @@
 		
 		if (orderBy!=null) buf.append(orderBy);
 		
-		if (lockMode!=null) {
-			buf.append( dialect.getForUpdateString(lockMode) );
+		if (lockOptions!=null) {
+			buf.append( dialect.getForUpdateString(lockOptions) );
 		}
 
 		return dialect.transformSelectString( buf.toString() );

Modified: core/trunk/entitymanager/src/test/java/org/hibernate/ejb/test/lock/LockTest.java
===================================================================
--- core/trunk/entitymanager/src/test/java/org/hibernate/ejb/test/lock/LockTest.java	2010-02-03 21:47:31 UTC (rev 18687)
+++ core/trunk/entitymanager/src/test/java/org/hibernate/ejb/test/lock/LockTest.java	2010-02-04 00:07:31 UTC (rev 18688)
@@ -3,14 +3,22 @@
 
 import javax.persistence.EntityManager;
 import javax.persistence.LockModeType;
+import javax.persistence.LockTimeoutException;
 import javax.persistence.OptimisticLockException;
+import javax.persistence.Query;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.hibernate.dialect.HSQLDialect;
+import org.hibernate.dialect.Oracle10gDialect;
 import org.hibernate.ejb.test.TestCase;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -187,7 +195,7 @@
 		Lock lock = new Lock();
 		Thread t = null;
 		try {
-			lock.setName( "contendedLock" );
+			lock.setName( "testContendedPessimisticLock" );
 
 			em.getTransaction().begin();
 			em.persist( lock );
@@ -204,21 +212,24 @@
 
 			t = new Thread( new Runnable() {
 				public void run() {
-
-					em2.getTransaction().begin();
-					log.info("testContendedPessimisticLock: (BG) about to read write-locked entity");
-					// we should block on the following read
-					Lock lock2 = em2.getReference( Lock.class, id );
-					lock2.getName();		//  force entity to be read
-					log.info("testContendedPessimisticLock: (BG) read write-locked entity");
-					em2.lock( lock2, LockModeType.PESSIMISTIC_READ);
-					log.info("testContendedPessimisticLock: (BG) got read lock on entity");
-					em2.getTransaction().commit();
-					latch.countDown();	// signal that we got the read lock
+					try {
+						em2.getTransaction().begin();
+						log.info("testContendedPessimisticLock: (BG) about to issue (PESSIMISTIC_READ) query against write-locked entity");
+						// we should block on the following read
+						Query query = em2.createQuery(
+								  "select L from Lock_ L where L.id < 10000 ");
+						query.setLockMode(LockModeType.PESSIMISTIC_READ);
+						List<Lock> resultList = query.getResultList();
+						resultList.get(0).getName(); //  force entity to be read
+					}
+					finally {
+						em2.getTransaction().commit();
+						latch.countDown();	// signal that we got the read lock
+					}
 				}
 			} );
 
-			// t.setDaemon( true );
+			t.setDaemon( true );
 			t.setName("LockTest read lock");
 			t.start();
 			log.info("testContendedPessimisticLock:  wait on BG thread");
@@ -245,6 +256,263 @@
 		}
 	}
 
+	public void testContendedPessimisticReadLockTimeout() throws Exception {
+
+		EntityManager em = getOrCreateEntityManager();
+		final EntityManager em2 = createIsolatedEntityManager();
+		// TODO:  replace dialect instanceof test with a Dialect.hasCapability (e.g. supportsPessimisticLockTimeout)
+		if ( ! (getDialect() instanceof Oracle10gDialect)) {
+			log.info("skipping testContendedPessimisticReadLockTimeout");
+			return;
+		}
+		Lock lock = new Lock();
+		Thread t = null;
+		FutureTask<Boolean> bgTask = null;
+		final CountDownLatch latch = new CountDownLatch(1);
+		try {
+			lock.setName( "testContendedPessimisticReadLockTimeout" );
+
+			em.getTransaction().begin();
+			em.persist( lock );
+			em.getTransaction().commit();
+			em.clear();
+
+			em.getTransaction().begin();
+			lock = em.getReference( Lock.class, lock.getId() );
+			em.lock( lock, LockModeType.PESSIMISTIC_WRITE );
+			final Integer id = lock.getId();
+			lock.getName();		// force entity to be read
+			log.info("testContendedPessimisticReadLockTimeout: got write lock");
+
+			bgTask = new FutureTask<Boolean>( new Callable() {
+				public Boolean call() {
+					try {
+						boolean timedOut = false;	// true (success) if LockTimeoutException occurred  
+						em2.getTransaction().begin();
+						log.info("testContendedPessimisticReadLockTimeout: (BG) about to read write-locked entity");
+						// we should block on the following read
+						Lock lock2 = em2.getReference( Lock.class, id );
+						lock2.getName();		//  force entity to be read
+						log.info("testContendedPessimisticReadLockTimeout: (BG) read write-locked entity");
+						Map<String,Object> props = new HashMap<String, Object>();
+						// timeout is in milliseconds
+						props.put("javax.persistence.lock.timeout", new Integer(1000));
+						try {
+							em2.lock( lock2, LockModeType.PESSIMISTIC_READ, props);
+						}
+						catch( LockTimeoutException e) {
+							// success
+							log.info("testContendedPessimisticReadLockTimeout: (BG) got expected timeout exception");
+							 timedOut = true;
+						}
+						catch ( Throwable e) {
+							log.info("Expected LockTimeoutException but got unexpected exception", e);
+						}
+						em2.getTransaction().commit();
+						return new Boolean(timedOut);
+					}
+					finally {
+						latch.countDown();	// signal that we finished
+					}
+				}
+			} );
+			t = new Thread(bgTask);
+			t.setDaemon( true );
+			t.setName("Lock timeout Test (bg)");
+			t.start();
+			boolean latchSet = latch.await( 10, TimeUnit.SECONDS );  // should return quickly on success
+			assertTrue( "background test thread finished (lock timeout is broken)", latchSet);
+			assertTrue( "background test thread timed out on lock attempt", bgTask.get().booleanValue() );
+			em.getTransaction().commit();
+		}
+		finally {
+			if ( em.getTransaction().isActive() ) {
+				em.getTransaction().rollback();
+			}
+			if ( t != null) {	  // wait for background thread to finish before deleting entity
+				t.join();
+			}
+			em.getTransaction().begin();
+			lock = em.getReference( Lock.class, lock.getId() );
+			em.remove( lock );
+			em.getTransaction().commit();
+			em.close();
+			em2.close();
+		}
+	}
+
+	public void testContendedPessimisticWriteLockTimeout() throws Exception {
+
+		EntityManager em = getOrCreateEntityManager();
+		final EntityManager em2 = createIsolatedEntityManager();
+		// TODO:  replace dialect instanceof test with a Dialect.hasCapability (e.g. supportsPessimisticLockTimeout)
+		if ( ! (getDialect() instanceof Oracle10gDialect)) {
+			log.info("skipping testContendedPessimisticWriteLockTimeout");
+			return;
+		}
+		Lock lock = new Lock();
+		Thread t = null;
+		FutureTask<Boolean> bgTask = null;
+		final CountDownLatch latch = new CountDownLatch(1);
+		try {
+			lock.setName( "testContendedPessimisticWriteLockTimeout" );
+
+			em.getTransaction().begin();
+			em.persist( lock );
+			em.getTransaction().commit();
+			em.clear();
+
+			em.getTransaction().begin();
+			lock = em.getReference( Lock.class, lock.getId() );
+			em.lock( lock, LockModeType.PESSIMISTIC_WRITE );
+			final Integer id = lock.getId();
+			lock.getName();		// force entity to be read
+			log.info("testContendedPessimisticWriteLockTimeout: got write lock");
+
+			bgTask = new FutureTask<Boolean>( new Callable() {
+				public Boolean call() {
+					try {
+						boolean timedOut = false;	// true (success) if LockTimeoutException occurred
+						em2.getTransaction().begin();
+						log.info("testContendedPessimisticWriteLockTimeout: (BG) about to read write-locked entity");
+						// we should block on the following read
+						Lock lock2 = em2.getReference( Lock.class, id );
+						lock2.getName();		//  force entity to be read
+						log.info("testContendedPessimisticWriteLockTimeout: (BG) read write-locked entity");
+						Map<String,Object> props = new HashMap<String, Object>();
+						// timeout is in milliseconds
+						props.put("javax.persistence.lock.timeout", new Integer(1000));
+						try {
+							em2.lock( lock2, LockModeType.PESSIMISTIC_WRITE, props);
+						}
+						catch( LockTimeoutException e) {
+							// success
+							log.info("testContendedPessimisticWriteLockTimeout: (BG) got expected timeout exception");
+							 timedOut = true;
+						}
+						catch ( Throwable e) {
+							log.info("Expected LockTimeoutException but got unexpected exception", e);
+						}
+						em2.getTransaction().commit();
+						return new Boolean(timedOut);
+					}
+					finally {
+						latch.countDown();	// signal that we finished
+					}
+				}
+			} );
+			t = new Thread(bgTask);
+			t.setDaemon( true );
+			t.setName("Lock timeout Test (bg)");
+			t.start();
+			boolean latchSet = latch.await( 10, TimeUnit.SECONDS );  // should return quickly on success
+			assertTrue( "background test thread finished (lock timeout is broken)", latchSet);
+			assertTrue( "background test thread timed out on lock attempt", bgTask.get().booleanValue() );
+			em.getTransaction().commit();
+		}
+		finally {
+			if ( em.getTransaction().isActive() ) {
+				em.getTransaction().rollback();
+			}
+			if ( t != null) {	  // wait for background thread to finish before deleting entity
+				t.join();
+			}
+			em.getTransaction().begin();
+			lock = em.getReference( Lock.class, lock.getId() );
+			em.remove( lock );
+			em.getTransaction().commit();
+			em.close();
+			em2.close();
+		}
+	}
+
+	public void testContendedPessimisticWriteLockNoWait() throws Exception {
+
+		EntityManager em = getOrCreateEntityManager();
+		final EntityManager em2 = createIsolatedEntityManager();
+		// TODO:  replace dialect instanceof test with a Dialect.hasCapability (e.g. supportsPessimisticLockTimeout)
+		if ( ! (getDialect() instanceof Oracle10gDialect)) {
+			log.info("skipping testContendedPessimisticWriteLockNoWait");
+			return;
+		}
+		Lock lock = new Lock();
+		Thread t = null;
+		FutureTask<Boolean> bgTask = null;
+		final CountDownLatch latch = new CountDownLatch(1);
+		try {
+			lock.setName( "testContendedPessimisticWriteLockNoWait" );
+
+			em.getTransaction().begin();
+			em.persist( lock );
+			em.getTransaction().commit();
+			em.clear();
+
+			em.getTransaction().begin();
+			lock = em.getReference( Lock.class, lock.getId() );
+			em.lock( lock, LockModeType.PESSIMISTIC_WRITE );
+			final Integer id = lock.getId();
+			lock.getName();		// force entity to be read
+			log.info("testContendedPessimisticWriteLockNoWait: got write lock");
+
+			bgTask = new FutureTask<Boolean>( new Callable() {
+				public Boolean call() {
+					try {
+						boolean timedOut = false;	// true (success) if LockTimeoutException occurred
+						em2.getTransaction().begin();
+						log.info("testContendedPessimisticWriteLockNoWait: (BG) about to read write-locked entity");
+						// we should block on the following read
+						Lock lock2 = em2.getReference( Lock.class, id );
+						lock2.getName();		//  force entity to be read
+						log.info("testContendedPessimisticWriteLockNoWait: (BG) read write-locked entity");
+						Map<String,Object> props = new HashMap<String, Object>();
+						// timeout of zero means no wait (for lock)
+						props.put("javax.persistence.lock.timeout", new Integer(0));
+						try {
+							em2.lock( lock2, LockModeType.PESSIMISTIC_WRITE, props);
+						}
+						catch( LockTimeoutException e) {
+							// success
+							log.info("testContendedPessimisticWriteLockNoWait: (BG) got expected timeout exception");
+							 timedOut = true;
+						}
+						catch ( Throwable e) {
+							log.info("Expected LockTimeoutException but got unexpected exception", e);
+						}
+						em2.getTransaction().commit();
+						return new Boolean(timedOut);
+					}
+					finally {
+						latch.countDown();	// signal that we finished
+					}
+				}
+			} );
+			t = new Thread(bgTask);
+			t.setDaemon( true );
+			t.setName("Lock timeout Test (bg)");
+			t.start();
+			boolean latchSet = latch.await( 10, TimeUnit.SECONDS );  // should return quickly on success
+			assertTrue( "background test thread finished (lock timeout is broken)", latchSet);
+			assertTrue( "background test thread timed out on lock attempt", bgTask.get().booleanValue() );
+			em.getTransaction().commit();
+		}
+		finally {
+			if ( em.getTransaction().isActive() ) {
+				em.getTransaction().rollback();
+			}
+			if ( t != null) {	  // wait for background thread to finish before deleting entity
+				t.join();
+			}
+			em.getTransaction().begin();
+			lock = em.getReference( Lock.class, lock.getId() );
+			em.remove( lock );
+			em.getTransaction().commit();
+			em.close();
+			em2.close();
+		}
+	}
+
+
+
 	public Class[] getAnnotatedClasses() {
 		return new Class[]{
 				Lock.class,



More information about the hibernate-commits mailing list