HHH-4765 Enhance Dialect support for JPA-2 locking

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18681 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Scott Marlow 2010-02-02 17:03:05 +00:00
parent d761d3ef45
commit 889470af88
3 changed files with 104 additions and 4 deletions

View File

@ -30,6 +30,7 @@ import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import org.hibernate.Hibernate; import org.hibernate.Hibernate;
import org.hibernate.LockOptions;
import org.hibernate.cfg.Environment; import org.hibernate.cfg.Environment;
import org.hibernate.dialect.function.NoArgSQLFunction; import org.hibernate.dialect.function.NoArgSQLFunction;
import org.hibernate.dialect.function.PositionSubstringFunction; import org.hibernate.dialect.function.PositionSubstringFunction;
@ -359,15 +360,22 @@ public class PostgreSQLDialect extends Dialect {
return false; return false;
} }
// locking support
public String getForUpdateString() { public String getForUpdateString() {
return " for update"; return " for update";
} }
public String getWriteLockString(int timeout) { public String getWriteLockString(int timeout) {
if ( timeout == LockOptions.NO_WAIT )
return " for update nowait";
else
return " for update"; return " for update";
} }
public String getReadLockString(int timeout) { public String getReadLockString(int timeout) {
if ( timeout == LockOptions.NO_WAIT )
return " for share nowait";
else
return " for share"; return " for share";
} }

View File

@ -28,6 +28,7 @@ import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
@ -55,6 +56,7 @@ public abstract class TestCase extends HibernateTestCase {
protected static EntityManagerFactory factory; protected static EntityManagerFactory factory;
private EntityManager em; private EntityManager em;
private ArrayList isolatedEms = new ArrayList();
public TestCase() { public TestCase() {
@ -80,7 +82,7 @@ public abstract class TestCase extends HibernateTestCase {
factory = ejbconfig.createEntityManagerFactory( getConfig() ); factory = ejbconfig.createEntityManagerFactory( getConfig() );
} }
protected void handleUnclosedResources(){ private void cleanUnclosed(EntityManager em){
if(em == null) { if(em == null) {
return; return;
} }
@ -94,6 +96,13 @@ public abstract class TestCase extends HibernateTestCase {
em.close(); em.close();
log.warn( "The EntityManager is not closed. Closing it." ); log.warn( "The EntityManager is not closed. Closing it." );
} }
}
protected void handleUnclosedResources(){
cleanUnclosed( this.em );
for ( Iterator iter = isolatedEms.iterator(); iter.hasNext();) {
cleanUnclosed( (EntityManager)iter.next() );
}
cfg = null; cfg = null;
} }
@ -110,6 +119,12 @@ public abstract class TestCase extends HibernateTestCase {
return em; return em;
} }
protected EntityManager createIsolatedEntityManager() {
EntityManager isolatedEm = factory.createEntityManager( );
isolatedEms.add( isolatedEm );
return isolatedEm;
}
/** /**
* always reopen a new EM and clse the existing one * always reopen a new EM and clse the existing one
*/ */

View File

@ -5,13 +5,20 @@ import javax.persistence.EntityManager;
import javax.persistence.LockModeType; import javax.persistence.LockModeType;
import javax.persistence.OptimisticLockException; import javax.persistence.OptimisticLockException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.ejb.test.TestCase; import org.hibernate.ejb.test.TestCase;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/** /**
* @author Emmanuel Bernard * @author Emmanuel Bernard
*/ */
public class LockTest extends TestCase { public class LockTest extends TestCase {
private static final Log log = LogFactory.getLog( LockTest.class );
public void testLockRead() throws Exception { public void testLockRead() throws Exception {
Lock lock = new Lock(); Lock lock = new Lock();
lock.setName( "name" ); lock.setName( "name" );
@ -168,6 +175,76 @@ public class LockTest extends TestCase {
em.close(); em.close();
} }
public void testContendedPessimisticLock() throws Exception {
EntityManager em = getOrCreateEntityManager();
final EntityManager em2 = createIsolatedEntityManager();
// TODO: replace dialect instanceof test with a Dialect.hasCapability (e.g. supportsPessimisticWriteLock)
if ( getDialect() instanceof HSQLDialect) {
log.info("skipping testContendedPessimisticLock");
return;
}
Lock lock = new Lock();
Thread t = null;
try {
lock.setName( "contendedLock" );
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("testContendedPessimisticLock: got write lock");
final CountDownLatch latch = new CountDownLatch(1);
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
}
} );
// t.setDaemon( true );
t.setName("LockTest read lock");
t.start();
log.info("testContendedPessimisticLock: wait on BG thread");
boolean latchSet = latch.await( 10, TimeUnit.SECONDS );
// latchSet should be false (timeout) because the background thread
// shouldn't be able to get a read lock on write locked entity.
log.info("testContendedPessimisticLock: BG thread completed transaction");
assertFalse( "shouldn't be able to get read lock while another transaction has write lock",latchSet );
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() { public Class[] getAnnotatedClasses() {
return new Class[]{ return new Class[]{
Lock.class, Lock.class,