HHH-8518 - Validate EMF#addNamedQuery transfers all query settings

This commit is contained in:
Steve Ebersole 2013-09-18 14:17:59 -05:00
parent feaac3b917
commit 346d958c20
6 changed files with 296 additions and 99 deletions

View File

@ -378,6 +378,16 @@ public class SessionDelegatorBaseImpl implements SessionImplementor, Session {
return sessionImplementor.getLoadQueryInfluencers();
}
@Override
public Query createQuery(NamedQueryDefinition namedQueryDefinition) {
return sessionImplementor.createQuery( namedQueryDefinition );
}
@Override
public SQLQuery createSQLQuery(NamedSQLQueryDefinition namedQueryDefinition) {
return sessionImplementor.createSQLQuery( namedQueryDefinition );
}
// Delegates to Session
@Override

View File

@ -35,6 +35,7 @@ import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.cache.spi.CacheKey;
@ -389,4 +390,22 @@ public interface SessionImplementor extends Serializable, LobCreationContext {
* should never be null.
*/
public LoadQueryInfluencers getLoadQueryInfluencers();
/**
* Used from EntityManager
*
* @param namedQueryDefinition The named query definition
*
* @return The basic HQL/JPQL query (without saved settings applied)
*/
Query createQuery(NamedQueryDefinition namedQueryDefinition);
/**
* Used from EntityManager
*
* @param namedQueryDefinition The named query definition
*
* @return The basic SQL query (without saved settings applied)
*/
SQLQuery createSQLQuery(NamedSQLQueryDefinition namedQueryDefinition);
}

View File

@ -133,36 +133,50 @@ public abstract class AbstractSessionImpl
}
}
@Override
public Query createQuery(NamedQueryDefinition namedQueryDefinition) {
String queryString = namedQueryDefinition.getQueryString();
final Query query = new QueryImpl(
queryString,
namedQueryDefinition.getFlushMode(),
this,
getHQLQueryPlan( queryString, false ).getParameterMetadata()
);
query.setComment( "named HQL query " + namedQueryDefinition.getName() );
if ( namedQueryDefinition.getLockOptions() != null ) {
query.setLockOptions( namedQueryDefinition.getLockOptions() );
}
return query;
}
@Override
public SQLQuery createSQLQuery(NamedSQLQueryDefinition namedQueryDefinition) {
final ParameterMetadata parameterMetadata = factory.getQueryPlanCache().getSQLParameterMetadata( namedQueryDefinition.getQueryString() );
final SQLQuery query = new SQLQueryImpl(
namedQueryDefinition,
this,
parameterMetadata
);
query.setComment( "named native SQL query " + namedQueryDefinition.getName() );
return query;
}
@Override
public Query getNamedQuery(String queryName) throws MappingException {
errorIfClosed();
NamedQueryDefinition nqd = factory.getNamedQuery( queryName );
final Query query;
if ( nqd != null ) {
String queryString = nqd.getQueryString();
query = new QueryImpl(
queryString,
nqd.getFlushMode(),
this,
getHQLQueryPlan( queryString, false ).getParameterMetadata()
);
query.setComment( "named HQL query " + queryName );
if ( nqd.getLockOptions() != null ) {
query.setLockOptions( nqd.getLockOptions() );
}
query = createQuery( nqd );
}
else {
NamedSQLQueryDefinition nsqlqd = factory.getNamedSQLQuery( queryName );
if ( nsqlqd==null ) {
throw new MappingException( "Named query not known: " + queryName );
}
ParameterMetadata parameterMetadata = factory.getQueryPlanCache().getSQLParameterMetadata( nsqlqd.getQueryString() );
query = new SQLQueryImpl(
nsqlqd,
this,
parameterMetadata
);
query.setComment( "named native SQL query " + queryName );
query = createSQLQuery( nsqlqd );
nqd = nsqlqd;
}
initQuery( query, nqd );

View File

@ -86,14 +86,18 @@ import org.hibernate.StaleStateException;
import org.hibernate.TransientObjectException;
import org.hibernate.TypeMismatchException;
import org.hibernate.UnresolvableObjectException;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.lock.LockingStrategyException;
import org.hibernate.dialect.lock.OptimisticEntityLockException;
import org.hibernate.dialect.lock.PessimisticEntityLockException;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.query.spi.HQLQueryPlan;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryConstructorReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn;
import org.hibernate.engine.spi.NamedQueryDefinition;
import org.hibernate.engine.spi.NamedSQLQueryDefinition;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
@ -763,93 +767,178 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage
@Override
public Query createNamedQuery(String name) {
return buildQueryFromName( name, null );
}
private QueryImpl buildQueryFromName(String name, Class resultType) {
checkOpen();
try {
org.hibernate.Query namedQuery = internalGetSession().getNamedQuery( name );
try {
return new QueryImpl( namedQuery, this );
}
catch ( RuntimeException e ) {
throw convert( e );
// we can't just call Session#getNamedQuery because we need to apply stored setting at the JPA Query
// level too
final SessionFactoryImplementor sfi = entityManagerFactory.getSessionFactory();
// first try as hql/jpql query
{
final NamedQueryDefinition namedQueryDefinition = sfi.getNamedQueryRepository().getNamedQueryDefinition( name );
if ( namedQueryDefinition != null ) {
return createNamedJpqlQuery( namedQueryDefinition, resultType );
}
}
catch ( MappingException e ) {
throw convert(new IllegalArgumentException( "Named query not found: " + name ));
// then as a native (SQL) query
{
final NamedSQLQueryDefinition namedQueryDefinition = sfi.getNamedQueryRepository().getNamedSQLQueryDefinition( name );
if ( namedQueryDefinition != null ) {
return createNamedSqlQuery( namedQueryDefinition, resultType );
}
}
throw convert( new IllegalArgumentException( "No query defined for that name [" + name + "]" ) );
}
protected QueryImpl createNamedJpqlQuery(NamedQueryDefinition namedQueryDefinition, Class resultType) {
final org.hibernate.Query hibQuery = ( (SessionImplementor) internalGetSession() ).createQuery( namedQueryDefinition );
if ( resultType != null ) {
resultClassChecking( resultType, hibQuery );
}
return wrapAsJpaQuery( namedQueryDefinition, hibQuery );
}
protected QueryImpl wrapAsJpaQuery(NamedQueryDefinition namedQueryDefinition, org.hibernate.Query hibQuery) {
try {
final QueryImpl jpaQuery = new QueryImpl( hibQuery, this );
applySavedSettings( namedQueryDefinition, jpaQuery );
return jpaQuery;
}
catch ( RuntimeException e ) {
throw convert( e );
}
}
protected void applySavedSettings(NamedQueryDefinition namedQueryDefinition, QueryImpl jpaQuery) {
if ( namedQueryDefinition.isCacheable() ) {
jpaQuery.setHint( QueryHints.HINT_CACHEABLE, true );
if ( namedQueryDefinition.getCacheRegion() != null ) {
jpaQuery.setHint( QueryHints.HINT_CACHE_REGION, namedQueryDefinition.getCacheRegion() );
}
}
if ( namedQueryDefinition.getCacheMode() != null ) {
jpaQuery.setHint( QueryHints.HINT_CACHE_MODE, namedQueryDefinition.getCacheMode() );
}
if ( namedQueryDefinition.isReadOnly() ) {
jpaQuery.setHint( QueryHints.HINT_READONLY, true );
}
if ( namedQueryDefinition.getTimeout() != null ) {
jpaQuery.setHint( QueryHints.SPEC_HINT_TIMEOUT, namedQueryDefinition.getTimeout() * 1000 );
}
if ( namedQueryDefinition.getFetchSize() != null ) {
jpaQuery.setHint( QueryHints.HINT_FETCH_SIZE, namedQueryDefinition.getFetchSize() );
}
if ( namedQueryDefinition.getComment() != null ) {
jpaQuery.setHint( QueryHints.HINT_COMMENT, namedQueryDefinition.getComment() );
}
if ( namedQueryDefinition.getFirstResult() != null ) {
jpaQuery.setFirstResult( namedQueryDefinition.getFirstResult() );
}
if ( namedQueryDefinition.getMaxResults() != null ) {
jpaQuery.setMaxResults( namedQueryDefinition.getMaxResults() );
}
if ( namedQueryDefinition.getLockOptions() != null ) {
if ( namedQueryDefinition.getLockOptions().getLockMode() != null ) {
jpaQuery.setLockMode(
LockModeTypeHelper.getLockModeType( namedQueryDefinition.getLockOptions().getLockMode() )
);
}
}
if ( namedQueryDefinition.getFlushMode() != null ) {
if ( namedQueryDefinition.getFlushMode() == FlushMode.COMMIT ) {
jpaQuery.setFlushMode( FlushModeType.COMMIT );
}
else {
jpaQuery.setFlushMode( FlushModeType.AUTO );
}
}
}
protected QueryImpl createNamedSqlQuery(NamedSQLQueryDefinition namedQueryDefinition, Class resultType) {
if ( resultType != null ) {
resultClassChecking( resultType, namedQueryDefinition );
}
return wrapAsJpaQuery(
namedQueryDefinition,
( (SessionImplementor) internalGetSession() ).createSQLQuery( namedQueryDefinition )
);
}
protected void resultClassChecking(Class resultType, NamedSQLQueryDefinition namedQueryDefinition) {
final SessionFactoryImplementor sfi = entityManagerFactory.getSessionFactory();
final NativeSQLQueryReturn[] queryReturns;
if ( namedQueryDefinition.getQueryReturns() != null ) {
queryReturns = namedQueryDefinition.getQueryReturns();
}
else if ( namedQueryDefinition.getResultSetRef() != null ) {
final ResultSetMappingDefinition rsMapping = sfi.getResultSetMapping( namedQueryDefinition.getResultSetRef() );
queryReturns = rsMapping.getQueryReturns();
}
else {
throw new AssertionFailure( "Unsupported named query model. Please report the bug in Hibernate EntityManager");
}
if ( queryReturns.length > 1 ) {
throw new IllegalArgumentException( "Cannot create TypedQuery for query with more than one return" );
}
final NativeSQLQueryReturn nativeSQLQueryReturn = queryReturns[0];
if ( nativeSQLQueryReturn instanceof NativeSQLQueryRootReturn ) {
final Class<?> actualReturnedClass;
final String entityClassName = ( (NativeSQLQueryRootReturn) nativeSQLQueryReturn ).getReturnEntityName();
try {
actualReturnedClass = sfi.getServiceRegistry().getService( ClassLoaderService.class ).classForName( entityClassName );
}
catch ( ClassLoadingException e ) {
throw new AssertionFailure(
"Unable to load class [" + entityClassName + "] declared on named native query [" +
namedQueryDefinition.getName() + "]"
);
}
if ( !resultType.isAssignableFrom( actualReturnedClass ) ) {
throw buildIncompatibleException( resultType, actualReturnedClass );
}
}
else if ( nativeSQLQueryReturn instanceof NativeSQLQueryConstructorReturn ) {
final NativeSQLQueryConstructorReturn ctorRtn = (NativeSQLQueryConstructorReturn) nativeSQLQueryReturn;
if ( !resultType.isAssignableFrom( ctorRtn.getTargetClass() ) ) {
throw buildIncompatibleException( resultType, ctorRtn.getTargetClass() );
}
}
else {
//TODO support other NativeSQLQueryReturn type. For now let it go.
}
}
@Override
public <T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass) {
checkOpen();
try {
/*
* Get the named query.
* If the named query is a SQL query, get the expected returned type from the query definition
* or its associated result set mapping
* If the named query is a HQL query, use getReturnType()
*/
org.hibernate.Query namedQuery = internalGetSession().getNamedQuery( name );
//TODO clean this up to avoid downcasting
final SessionFactoryImplementor factoryImplementor = entityManagerFactory.getSessionFactory();
final NamedSQLQueryDefinition queryDefinition = factoryImplementor.getNamedSQLQuery( name );
try {
if ( queryDefinition != null ) {
Class<?> actualReturnedClass;
final NativeSQLQueryReturn[] queryReturns;
if ( queryDefinition.getQueryReturns() != null ) {
queryReturns = queryDefinition.getQueryReturns();
}
else if ( queryDefinition.getResultSetRef() != null ) {
final ResultSetMappingDefinition rsMapping = factoryImplementor.getResultSetMapping(
queryDefinition.getResultSetRef()
);
queryReturns = rsMapping.getQueryReturns();
}
else {
throw new AssertionFailure( "Unsupported named query model. Please report the bug in Hibernate EntityManager");
}
if ( queryReturns.length > 1 ) {
throw new IllegalArgumentException( "Cannot create TypedQuery for query with more than one return" );
}
final NativeSQLQueryReturn nativeSQLQueryReturn = queryReturns[0];
if ( nativeSQLQueryReturn instanceof NativeSQLQueryRootReturn ) {
final String entityClassName = ( ( NativeSQLQueryRootReturn ) nativeSQLQueryReturn ).getReturnEntityName();
try {
actualReturnedClass = ReflectHelper.classForName( entityClassName, AbstractEntityManagerImpl.class );
}
catch ( ClassNotFoundException e ) {
throw new AssertionFailure( "Unable to instantiate class declared on named native query: " + name + " " + entityClassName );
}
if ( !resultClass.isAssignableFrom( actualReturnedClass ) ) {
throw buildIncompatibleException( resultClass, actualReturnedClass );
}
}
else {
//TODO support other NativeSQLQueryReturn type. For now let it go.
}
}
else {
resultClassChecking( resultClass, namedQuery );
}
return new QueryImpl<T>( namedQuery, this );
}
catch ( RuntimeException e ) {
throw convert( e );
}
}
catch ( MappingException e ) {
throw convert(new IllegalArgumentException( "Named query not found: " + name ));
}
return buildQueryFromName( name, resultClass );
}
private IllegalArgumentException buildIncompatibleException(Class<?> resultClass, Class<?> actualResultClass) {
return new IllegalArgumentException(
"Type specified for TypedQuery [" +
resultClass.getName() +
"] is incompatible with query return type [" +
actualResultClass + "]"
);
"Type specified for TypedQuery [" + resultClass.getName() +
"] is incompatible with query return type [" + actualResultClass + "]"
);
}
@Override

View File

@ -24,6 +24,7 @@
package org.hibernate.jpa.spi;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.Parameter;
import javax.persistence.TemporalType;
import javax.persistence.TransactionRequiredException;
@ -118,12 +119,18 @@ public abstract class AbstractQueryImpl<X> extends BaseQueryImpl implements Type
@SuppressWarnings({ "unchecked" })
public TypedQuery<X> setLockMode(javax.persistence.LockModeType lockModeType) {
checkOpen( true );
if (! getEntityManager().isTransactionInProgress()) {
throw new TransactionRequiredException( "no transaction is in progress" );
// todo : technically this check should be on execution of the query, not here : HHH-8521
if ( lockModeType != LockModeType.NONE ) {
if (! getEntityManager().isTransactionInProgress()) {
throw new TransactionRequiredException( "no transaction is in progress" );
}
}
if ( ! canApplyAliasSpecificLockModeHints() ) {
throw new IllegalStateException( "Not a JPAQL/Criteria query" );
}
this.jpaLockMode = lockModeType;
internalApplyLockMode( lockModeType );
return this;

View File

@ -24,6 +24,8 @@
package org.hibernate.jpa.test.query;
import javax.persistence.EntityManager;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.Query;
import org.hibernate.CacheMode;
@ -31,6 +33,8 @@ import org.hibernate.FlushMode;
import org.hibernate.LockMode;
import org.hibernate.ejb.HibernateQuery;
import org.hibernate.ejb.QueryHints;
import org.hibernate.engine.spi.NamedQueryDefinition;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.jpa.test.Distributor;
import org.hibernate.jpa.test.Item;
@ -69,6 +73,60 @@ public class AddNamedQueryTest extends BaseEntityManagerFunctionalTestCase {
em.close();
}
@Test
public void testLockModeHandling() {
final String name = "lock-mode-handling";
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
Query q = em.createQuery( "from Item" );
assertEquals( LockModeType.NONE, q.getLockMode() );
q.setLockMode( LockModeType.OPTIMISTIC );
assertEquals( LockModeType.OPTIMISTIC, q.getLockMode() );
em.getEntityManagerFactory().addNamedQuery( name, q );
// first, lets check the underlying stored query def
SessionFactoryImplementor sfi = entityManagerFactory().unwrap( SessionFactoryImplementor.class );
NamedQueryDefinition def = sfi.getNamedQueryRepository().getNamedQueryDefinition( name );
assertEquals( LockMode.OPTIMISTIC, def.getLockOptions().getLockMode() );
// then lets create a query by name and check its setting
q = em.createNamedQuery( name );
assertEquals( LockMode.OPTIMISTIC, q.unwrap( org.hibernate.Query.class ).getLockOptions().getLockMode() );
assertEquals( LockModeType.OPTIMISTIC, q.getLockMode() );
em.getTransaction().commit();
em.close();
}
@Test
public void testFlushModeHandling() {
final String name = "flush-mode-handling";
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
Query q = em.createQuery( "from Item" );
assertEquals( FlushModeType.AUTO, q.getFlushMode() );
q.setFlushMode( FlushModeType.COMMIT );
assertEquals( FlushModeType.COMMIT, q.getFlushMode() );
em.getEntityManagerFactory().addNamedQuery( name, q );
// first, lets check the underlying stored query def
SessionFactoryImplementor sfi = entityManagerFactory().unwrap( SessionFactoryImplementor.class );
NamedQueryDefinition def = sfi.getNamedQueryRepository().getNamedQueryDefinition( name );
assertEquals( FlushMode.COMMIT, def.getFlushMode() );
// then lets create a query by name and check its setting
q = em.createNamedQuery( name );
assertEquals( FlushMode.COMMIT, q.unwrap( org.hibernate.Query.class ).getFlushMode() );
assertEquals( FlushModeType.COMMIT, q.getFlushMode() );
em.getTransaction().commit();
em.close();
}
@Test
public void testConfigValueHandling() {
final String name = "itemJpaQueryWithLockModeAndHints";
@ -78,7 +136,7 @@ public class AddNamedQueryTest extends BaseEntityManagerFunctionalTestCase {
// assert the state of the query config settings based on the initial named query
assertNull( hibernateQuery.getFirstResult() );
assertNull( hibernateQuery.getMaxResults() );
assertEquals( FlushMode.MANUAL, hibernateQuery.getFlushMode() ); // todo : we need to fix this to stick to AUTO/COMMIT when used from JPA
assertEquals( FlushMode.AUTO, hibernateQuery.getFlushMode() );
assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() );
assertEquals( (Integer) 3, hibernateQuery.getTimeout() ); // jpa timeout is in milliseconds, whereas Hibernate's is in seconds
@ -91,7 +149,7 @@ public class AddNamedQueryTest extends BaseEntityManagerFunctionalTestCase {
// assert the state of the query config settings based on the initial named query
assertNull( hibernateQuery.getFirstResult() );
assertNull( hibernateQuery.getMaxResults() );
assertEquals( FlushMode.MANUAL, hibernateQuery.getFlushMode() );
assertEquals( FlushMode.AUTO, hibernateQuery.getFlushMode() );
assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() );
assertEquals( (Integer) 10, hibernateQuery.getTimeout() );
@ -104,7 +162,7 @@ public class AddNamedQueryTest extends BaseEntityManagerFunctionalTestCase {
// assert the state of the query config settings based on the initial named query
assertNull( hibernateQuery.getFirstResult() );
assertNull( hibernateQuery.getMaxResults() );
assertEquals( FlushMode.MANUAL, hibernateQuery.getFlushMode() );
assertEquals( FlushMode.AUTO, hibernateQuery.getFlushMode() );
assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() );
assertEquals( (Integer) 10, hibernateQuery.getTimeout() );
@ -117,7 +175,7 @@ public class AddNamedQueryTest extends BaseEntityManagerFunctionalTestCase {
// assert the state of the query config settings based on the initial named query
assertEquals( (Integer) 51, hibernateQuery.getFirstResult() );
assertNull( hibernateQuery.getMaxResults() );
assertEquals( FlushMode.MANUAL, hibernateQuery.getFlushMode() );
assertEquals( FlushMode.AUTO, hibernateQuery.getFlushMode() );
assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() );
assertEquals( (Integer) 10, hibernateQuery.getTimeout() );