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:
parent
d761d3ef45
commit
889470af88
|
@ -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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue