HHH-4662 Implement javax.persistence.query.timeout

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18852 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Scott Marlow 2010-02-22 23:27:16 +00:00
parent f3aa0c63bb
commit 02451f12fb
6 changed files with 136 additions and 8 deletions

View File

@ -1,7 +1,7 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * Hibernate, Relational Persistence for Idiomatic Java
* *
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as * Copyright (c) 2010, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC. * distributed under license by Red Hat Middleware LLC.
@ -69,7 +69,7 @@ public abstract class QueryBinder {
queryName, queryName,
getBoolean( queryName, "org.hibernate.cacheable", hints ), getBoolean( queryName, "org.hibernate.cacheable", hints ),
getString( queryName, "org.hibernate.cacheRegion", hints ), getString( queryName, "org.hibernate.cacheRegion", hints ),
getInteger( queryName, "org.hibernate.timeout", hints ), getTimeout( queryName, hints ),
getInteger( queryName, "org.hibernate.fetchSize", hints ), getInteger( queryName, "org.hibernate.fetchSize", hints ),
getFlushMode( queryName, hints ), getFlushMode( queryName, hints ),
getCacheMode( queryName, hints ), getCacheMode( queryName, hints ),
@ -105,7 +105,7 @@ public abstract class QueryBinder {
null, null,
getBoolean( queryName, "org.hibernate.cacheable", hints ), getBoolean( queryName, "org.hibernate.cacheable", hints ),
getString( queryName, "org.hibernate.cacheRegion", hints ), getString( queryName, "org.hibernate.cacheRegion", hints ),
getInteger( queryName, "org.hibernate.timeout", hints ), getTimeout( queryName, hints ),
getInteger( queryName, "org.hibernate.fetchSize", hints ), getInteger( queryName, "org.hibernate.fetchSize", hints ),
getFlushMode( queryName, hints ), getFlushMode( queryName, hints ),
getCacheMode( queryName, hints ), getCacheMode( queryName, hints ),
@ -126,7 +126,7 @@ public abstract class QueryBinder {
null, null,
getBoolean( queryName, "org.hibernate.cacheable", hints ), getBoolean( queryName, "org.hibernate.cacheable", hints ),
getString( queryName, "org.hibernate.cacheRegion", hints ), getString( queryName, "org.hibernate.cacheRegion", hints ),
getInteger( queryName, "org.hibernate.timeout", hints ), getTimeout( queryName, hints ),
getInteger( queryName, "org.hibernate.fetchSize", hints ), getInteger( queryName, "org.hibernate.fetchSize", hints ),
getFlushMode( queryName, hints ), getFlushMode( queryName, hints ),
getCacheMode( queryName, hints ), getCacheMode( queryName, hints ),
@ -407,4 +407,18 @@ public abstract class QueryBinder {
} }
return null; return null;
} }
private static Integer getTimeout(String queryName, QueryHint[] hints) {
Integer timeout = getInteger( queryName, "javax.persistence.query.timeout", hints );
if ( timeout != null ) {
// convert milliseconds to seconds
timeout = new Integer ((int)Math.round(timeout.doubleValue() / 1000.0 ) );
}
else {
// timeout is already in seconds
timeout = getInteger( queryName, "org.hibernate.timeout", hints );
}
return timeout;
}
} }

View File

@ -1,7 +1,7 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * Hibernate, Relational Persistence for Idiomatic Java
* *
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as * Copyright (c) 2010, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC. * distributed under license by Red Hat Middleware LLC.
@ -26,6 +26,7 @@ package org.hibernate.exception;
import org.hibernate.JDBCException; import org.hibernate.JDBCException;
import org.hibernate.PessimisticLockException; import org.hibernate.PessimisticLockException;
import org.hibernate.QueryTimeoutException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashSet; import java.util.HashSet;
@ -115,6 +116,13 @@ public class SQLStateConverter implements SQLExceptionConverter {
// Derby "A lock could not be obtained within the time requested." // Derby "A lock could not be obtained within the time requested."
return new PessimisticLockException( message, sqlException, sql ); return new PessimisticLockException( message, sqlException, sql );
} }
// MySQL Query execution was interrupted
if ( "70100".equals( sqlState ) ||
// Oracle user requested cancel of current operation
"72000".equals( sqlState ) ) {
throw new QueryTimeoutException( message, sqlException, sql );
}
} }
return handledNonSpecificException( sqlException, message, sql ); return handledNonSpecificException( sqlException, message, sql );

View File

@ -1130,6 +1130,11 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage
handlePersistenceException( converted ); handlePersistenceException( converted );
return converted; return converted;
} }
else if ( e instanceof org.hibernate.QueryTimeoutException ) {
QueryTimeoutException converted = new QueryTimeoutException(e.getMessage(), e);
handlePersistenceException( converted );
return converted;
}
else if ( e instanceof ObjectNotFoundException ) { else if ( e instanceof ObjectNotFoundException ) {
EntityNotFoundException converted = new EntityNotFoundException( e.getMessage() ); EntityNotFoundException converted = new EntityNotFoundException( e.getMessage() );
handlePersistenceException( converted ); handlePersistenceException( converted );

View File

@ -1,7 +1,7 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * Hibernate, Relational Persistence for Idiomatic Java
* *
* Copyright (c) 2009 by Red Hat Inc and/or its affiliates or by * Copyright (c) 2010 by Red Hat Inc and/or its affiliates or by
* third-party contributors as indicated by either @author tags or express * third-party contributors as indicated by either @author tags or express
* copyright attribution statements applied by the authors. All * copyright attribution statements applied by the authors. All
* third-party contributions are distributed under license by Red Hat Inc. * third-party contributions are distributed under license by Red Hat Inc.
@ -50,6 +50,7 @@ import static org.hibernate.ejb.QueryHints.HINT_FETCH_SIZE;
import static org.hibernate.ejb.QueryHints.HINT_FLUSH_MODE; import static org.hibernate.ejb.QueryHints.HINT_FLUSH_MODE;
import static org.hibernate.ejb.QueryHints.HINT_READONLY; import static org.hibernate.ejb.QueryHints.HINT_READONLY;
import static org.hibernate.ejb.QueryHints.HINT_TIMEOUT; import static org.hibernate.ejb.QueryHints.HINT_TIMEOUT;
import static org.hibernate.ejb.QueryHints.SPEC_HINT_TIMEOUT;
import org.hibernate.ejb.util.CacheModeHelper; import org.hibernate.ejb.util.CacheModeHelper;
import org.hibernate.ejb.util.ConfigurationHelper; import org.hibernate.ejb.util.ConfigurationHelper;
@ -204,6 +205,11 @@ public abstract class AbstractQueryImpl<X> implements TypedQuery<X> {
if ( HINT_TIMEOUT.equals( hintName ) ) { if ( HINT_TIMEOUT.equals( hintName ) ) {
applyTimeout( ConfigurationHelper.getInteger( value ) ); applyTimeout( ConfigurationHelper.getInteger( value ) );
} }
else if ( SPEC_HINT_TIMEOUT.equals( hintName ) ) {
// convert milliseconds to seconds
int timeout = (int)Math.round(ConfigurationHelper.getInteger( value ).doubleValue() / 1000.0 );
applyTimeout( new Integer(timeout) );
}
else if ( HINT_COMMENT.equals( hintName ) ) { else if ( HINT_COMMENT.equals( hintName ) ) {
applyComment( (String) value ); applyComment( (String) value );
} }

View File

@ -1,7 +1,7 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * Hibernate, Relational Persistence for Idiomatic Java
* *
* Copyright (c) 2009 by Red Hat Inc and/or its affiliates or by * Copyright (c) 2010 by Red Hat Inc and/or its affiliates or by
* third-party contributors as indicated by either @author tags or express * third-party contributors as indicated by either @author tags or express
* copyright attribution statements applied by the authors. All * copyright attribution statements applied by the authors. All
* third-party contributions are distributed under license by Red Hat Inc. * third-party contributions are distributed under license by Red Hat Inc.
@ -32,7 +32,12 @@ import java.util.HashSet;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class QueryHints { public class QueryHints {
public static final String HINT_TIMEOUT = "org.hibernate.timeout"; /**
* @deprecated HINT_TIMEOUT (org.hibernate.timeout),
* instead use SPEC_HINT_TIMEOUT (javax.persistence.query.timeout)
*/
public static final String HINT_TIMEOUT = "org.hibernate.timeout"; // Query timeout in seconds
public static final String SPEC_HINT_TIMEOUT = "javax.persistence.query.timeout"; // timeout in milliseconds
public static final String HINT_COMMENT = "org.hibernate.comment"; public static final String HINT_COMMENT = "org.hibernate.comment";
public static final String HINT_FETCH_SIZE = "org.hibernate.fetchSize"; public static final String HINT_FETCH_SIZE = "org.hibernate.fetchSize";
public static final String HINT_CACHE_REGION = "org.hibernate.cacheRegion"; public static final String HINT_CACHE_REGION = "org.hibernate.cacheRegion";
@ -46,6 +51,7 @@ public class QueryHints {
private static Set<String> buildHintsSet() { private static Set<String> buildHintsSet() {
HashSet<String> hints = new HashSet<String>(); HashSet<String> hints = new HashSet<String>();
hints.add( HINT_TIMEOUT ); hints.add( HINT_TIMEOUT );
hints.add( SPEC_HINT_TIMEOUT );
hints.add( HINT_COMMENT ); hints.add( HINT_COMMENT );
hints.add( HINT_FETCH_SIZE ); hints.add( HINT_FETCH_SIZE );
hints.add( HINT_CACHE_REGION ); hints.add( HINT_CACHE_REGION );

View File

@ -6,6 +6,7 @@ import javax.persistence.LockModeType;
import javax.persistence.LockTimeoutException; import javax.persistence.LockTimeoutException;
import javax.persistence.OptimisticLockException; import javax.persistence.OptimisticLockException;
import javax.persistence.Query; import javax.persistence.Query;
import javax.persistence.QueryTimeoutException;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -511,6 +512,94 @@ public class LockTest extends TestCase {
} }
} }
public void testQueryTimeout() throws Exception {
EntityManager em = getOrCreateEntityManager();
final EntityManager em2 = createIsolatedEntityManager();
// TODO: replace dialect instanceof test with a Dialect.hasCapability
if ( ! (getDialect() instanceof Oracle10gDialect)) {
log.info("skipping testQueryTimeout");
return;
}
Lock lock = new Lock();
Thread t = null;
FutureTask<Boolean> 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<Boolean>( 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 );
query.setHint( "javax.persistence.query.timeout", new Integer(500) ); // 1 sec timeout
List<Lock> 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() { public Class[] getAnnotatedClasses() {