diff --git a/entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java b/entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java index e65005b701..fce47a3570 100755 --- a/entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java +++ b/entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java @@ -118,6 +118,7 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage entityManagerSpecificProperties.add( AvailableSettings.FLUSH_MODE ); entityManagerSpecificProperties.add( AvailableSettings.SHARED_CACHE_RETRIEVE_MODE ); entityManagerSpecificProperties.add( AvailableSettings.SHARED_CACHE_STORE_MODE ); + entityManagerSpecificProperties.add( QueryHints.SPEC_HINT_TIMEOUT ); } private EntityManagerFactoryImpl entityManagerFactory; @@ -168,6 +169,17 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage ); } + private Query applyProperties(Query query) { + if ( lockOptions.getLockMode() != LockMode.NONE ) { + query.setLockMode( getLockMode(lockOptions.getLockMode())); + } + Object queryTimeout; + if ( (queryTimeout = getProperties().get(QueryHints.SPEC_HINT_TIMEOUT)) != null ) { + query.setHint ( QueryHints.SPEC_HINT_TIMEOUT, queryTimeout ); + } + return query; + } + private CacheRetrieveMode currentCacheRetrieveMode() { return determineCacheRetrieveMode( properties ); } @@ -248,7 +260,7 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage public Query createQuery(String jpaqlString) { try { - return new QueryImpl( getSession().createQuery( jpaqlString ), this ); + return applyProperties( new QueryImpl( getSession().createQuery( jpaqlString ), this ) ); } catch ( HibernateException he ) { throw convert( he ); diff --git a/entitymanager/src/test/java/org/hibernate/ejb/test/TestCase.java b/entitymanager/src/test/java/org/hibernate/ejb/test/TestCase.java index 5839877c4b..b66b8c13b8 100644 --- a/entitymanager/src/test/java/org/hibernate/ejb/test/TestCase.java +++ b/entitymanager/src/test/java/org/hibernate/ejb/test/TestCase.java @@ -1,4 +1,4 @@ -// $Id:$ +// $Id$ /* * Hibernate, Relational Persistence for Idiomatic Java * @@ -134,6 +134,12 @@ public abstract class TestCase extends HibernateTestCase { return isolatedEm; } + protected EntityManager createIsolatedEntityManager(Map props) { + EntityManager isolatedEm = factory.createEntityManager(props); + isolatedEms.add( isolatedEm ); + return isolatedEm; + } + /** * always reopen a new EM and clse the existing one */ diff --git a/entitymanager/src/test/java/org/hibernate/ejb/test/lock/LockTest.java b/entitymanager/src/test/java/org/hibernate/ejb/test/lock/LockTest.java index 205fa2ef5c..cefe6d29dd 100644 --- a/entitymanager/src/test/java/org/hibernate/ejb/test/lock/LockTest.java +++ b/entitymanager/src/test/java/org/hibernate/ejb/test/lock/LockTest.java @@ -601,6 +601,95 @@ public class LockTest extends TestCase { } } + public void testQueryTimeoutEMProps() throws Exception { + // TODO: replace dialect instanceof test with a Dialect.hasCapability + if ( ! (getDialect() instanceof Oracle10gDialect)) { + log.info("skipping testQueryTimeout"); + return; + } + EntityManager em = getOrCreateEntityManager(); + Map queryTimeoutProps = new HashMap(); + queryTimeoutProps.put("javax.persistence.query.timeout", new Integer(500) ); // 1 sec timeout (should round up) + final EntityManager em2 = createIsolatedEntityManager(queryTimeoutProps); + Lock lock = new Lock(); + Thread t = null; + FutureTask bgTask = null; + final CountDownLatch latch = new CountDownLatch(1); + try { + lock.setName( "testQueryTimeout" ); + + 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("testQueryTimeout: got write lock"); + + bgTask = new FutureTask( new Callable() { + public Boolean call() { + try { + boolean timedOut = false; // true (success) if LockTimeoutException occurred + em2.getTransaction().begin(); + log.info( "testQueryTimeout: (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( "testQueryTimeout: (BG) read write-locked entity" ); + try { + // 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 resultList = query.getResultList(); + String name = resultList.get(0).getName(); // force entity to be read + log.info( "testQueryTimeout: name read =" + name ); + } + catch( QueryTimeoutException e) { + // success + log.info( "testQueryTimeout: (BG) got expected timeout exception" ); + timedOut = true; + } + catch ( Throwable e) { + log.info( "testQueryTimeout: 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( "testQueryTimeout (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[]{