HHH-5348 - support for TypedQuery jpaql/hql "scalar" queries

This commit is contained in:
Steve Ebersole 2011-03-30 11:35:29 -05:00
parent 7501fdea53
commit d104f28a59
10 changed files with 308 additions and 61 deletions

View File

@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +20,6 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.engine.query;
@ -32,6 +31,9 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
import org.hibernate.HibernateException;
import org.hibernate.HibernateLogger;
import org.hibernate.QueryException;
@ -50,7 +52,6 @@ import org.hibernate.internal.util.collections.EmptyIterator;
import org.hibernate.internal.util.collections.IdentitySet;
import org.hibernate.internal.util.collections.JoinedIterator;
import org.hibernate.type.Type;
import org.jboss.logging.Logger;
/**
* Defines a query execution plan for an HQL query (or filter).
@ -331,4 +332,8 @@ public class HQLQueryPlan implements Serializable {
System.arraycopy(translators, 0, copy, 0, copy.length);
return copy;
}
public Class getDynamicInstantiationResultType() {
return translators[0].getDynamicInstantiationResultType();
}
}

View File

@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,13 +20,14 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.hql;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
@ -185,4 +186,6 @@ public interface QueryTranslator {
boolean containsCollectionFetches();
boolean isManipulationStatement();
public Class getDynamicInstantiationResultType();
}

View File

@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +20,6 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.hql.ast;
@ -30,6 +29,13 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import antlr.ANTLRException;
import antlr.RecognitionException;
import antlr.TokenStreamException;
import antlr.collections.AST;
import org.jboss.logging.Logger;
import org.hibernate.HibernateException;
import org.hibernate.HibernateLogger;
import org.hibernate.MappingException;
@ -50,6 +56,7 @@ import org.hibernate.hql.ast.exec.BasicExecutor;
import org.hibernate.hql.ast.exec.MultiTableDeleteExecutor;
import org.hibernate.hql.ast.exec.MultiTableUpdateExecutor;
import org.hibernate.hql.ast.exec.StatementExecutor;
import org.hibernate.hql.ast.tree.AggregatedSelectExpression;
import org.hibernate.hql.ast.tree.FromElement;
import org.hibernate.hql.ast.tree.InsertStatement;
import org.hibernate.hql.ast.tree.QueryNode;
@ -63,11 +70,6 @@ import org.hibernate.internal.util.collections.IdentitySet;
import org.hibernate.loader.hql.QueryLoader;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.type.Type;
import org.jboss.logging.Logger;
import antlr.ANTLRException;
import antlr.RecognitionException;
import antlr.TokenStreamException;
import antlr.collections.AST;
/**
* A QueryTranslator that uses an Antlr-based parser.
@ -564,6 +566,12 @@ public class QueryTranslatorImpl implements FilterTranslator {
return collectedParameterSpecifications;
}
@Override
public Class getDynamicInstantiationResultType() {
AggregatedSelectExpression aggregation = queryLoader.getAggregatedSelectExpression();
return aggregation == null ? null : aggregation.getAggregationResultType();
}
public static class JavaConstantConverter implements NodeTraverser.VisitationStrategy {
private AST dotRoot;
public void visit(AST node) {

View File

@ -1,8 +1,10 @@
/*
* Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2009-2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +22,9 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.hql.ast.tree;
import java.util.List;
import org.hibernate.transform.ResultTransformer;
/**
@ -50,4 +54,11 @@ public interface AggregatedSelectExpression extends SelectExpression {
* @return The appropriate transformer
*/
public ResultTransformer getResultTransformer();
/**
* Obtain the java type of the aggregation
*
* @return The java type.
*/
public Class getAggregationResultType();
}

View File

@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,13 +20,17 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.hql.ast.tree;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import antlr.SemanticException;
import antlr.collections.AST;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.QueryException;
import org.hibernate.hql.ast.DetailedSemanticException;
@ -36,8 +40,6 @@ import org.hibernate.transform.AliasToBeanConstructorResultTransformer;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.transform.Transformers;
import org.hibernate.type.Type;
import antlr.SemanticException;
import antlr.collections.AST;
/**
* Represents a constructor (new) in a SELECT.
@ -45,11 +47,11 @@ import antlr.collections.AST;
* @author josh
*/
public class ConstructorNode extends SelectExpressionList implements AggregatedSelectExpression {
private Class resultType;
private Constructor constructor;
private Type[] constructorArgumentTypes;
private boolean isMap;
private boolean isList;
private int scalarColumnIndex = -1;
public ResultTransformer getResultTransformer() {
if ( constructor != null ) {
@ -64,14 +66,6 @@ public class ConstructorNode extends SelectExpressionList implements AggregatedS
throw new QueryException( "Unable to determine proper dynamic-instantiation tranformer to use." );
}
public boolean isMap() {
return isMap;
}
public boolean isList() {
return isList;
}
private String[] aggregatedAliases;
public String[] getAggregatedAliases() {
@ -101,7 +95,7 @@ public class ConstructorNode extends SelectExpressionList implements AggregatedS
}
public int getScalarColumnIndex() {
return scalarColumnIndex;
return -1;
}
public void setScalarColumnText(int i) throws SemanticException {
@ -119,6 +113,11 @@ public class ConstructorNode extends SelectExpressionList implements AggregatedS
return getFirstChild().getNextSibling();
}
@Override
public Class getAggregationResultType() {
return resultType;
}
/**
* @deprecated (tell clover to ignore this method)
*/
@ -143,12 +142,15 @@ public class ConstructorNode extends SelectExpressionList implements AggregatedS
String path = ( ( PathNode ) getFirstChild() ).getPath();
if ( "map".equals( path.toLowerCase() ) ) {
isMap = true;
resultType = Map.class;
}
else if ( "list".equals( path.toLowerCase() ) ) {
isList = true;
resultType = List.class;
}
else {
constructor = resolveConstructor(path);
constructor = resolveConstructor( path );
resultType = constructor.getDeclaringClass();
}
}

View File

@ -63,6 +63,11 @@ public class MapEntryNode extends AbstractMapComponentNode implements Aggregated
return "entry(*)";
}
@Override
public Class getAggregationResultType() {
return Map.Entry.class;
}
protected Type resolveType(QueryableCollection collectionPersister) {
Type keyType = collectionPersister.getIndexType();
Type valueType = collectionPersister.getElementType();

View File

@ -1193,6 +1193,11 @@ public class QueryTranslatorImpl extends BasicLoader implements FilterTranslator
return false;
}
@Override
public Class getDynamicInstantiationResultType() {
return holderClass;
}
public ParameterTranslations getParameterTranslations() {
return new ParameterTranslations() {

View File

@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,9 +20,9 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.loader.hql;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -31,6 +31,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
@ -96,7 +97,7 @@ public class QueryLoader extends BasicLoader {
private int selectLength;
private ResultTransformer implicitResultTransformer;
private AggregatedSelectExpression aggregatedSelectExpression;
private String[] queryReturnAliases;
private LockMode[] defaultLockModes;
@ -128,10 +129,7 @@ public class QueryLoader extends BasicLoader {
//sqlResultTypes = selectClause.getSqlResultTypes();
queryReturnTypes = selectClause.getQueryReturnTypes();
AggregatedSelectExpression aggregatedSelectExpression = selectClause.getAggregatedSelectExpression();
implicitResultTransformer = aggregatedSelectExpression == null
? null
: aggregatedSelectExpression.getResultTransformer();
aggregatedSelectExpression = selectClause.getAggregatedSelectExpression();
queryReturnAliases = selectClause.getQueryReturnAliases();
List collectionFromElements = selectClause.getCollectionFromElements();
@ -199,6 +197,11 @@ public class QueryLoader extends BasicLoader {
defaultLockModes = ArrayHelper.fillArray( LockMode.NONE, size );
}
public AggregatedSelectExpression getAggregatedSelectExpression() {
return aggregatedSelectExpression;
}
// -- Loader implementation --
public final void validateScrollability() throws HibernateException {
@ -375,7 +378,7 @@ public class QueryLoader extends BasicLoader {
}
private boolean hasSelectNew() {
return implicitResultTransformer != null;
return aggregatedSelectExpression != null && aggregatedSelectExpression.getResultTransformer() != null;
}
protected String[] getResultRowAliases() {
@ -383,6 +386,9 @@ public class QueryLoader extends BasicLoader {
}
protected ResultTransformer resolveResultTransformer(ResultTransformer resultTransformer) {
final ResultTransformer implicitResultTransformer = aggregatedSelectExpression == null
? null
: aggregatedSelectExpression.getResultTransformer();
return HolderInstantiator.resolveResultTransformer( implicitResultTransformer, resultTransformer );
}
@ -446,6 +452,9 @@ public class QueryLoader extends BasicLoader {
}
private HolderInstantiator buildHolderInstantiator(ResultTransformer queryLocalResultTransformer) {
final ResultTransformer implicitResultTransformer = aggregatedSelectExpression == null
? null
: aggregatedSelectExpression.getResultTransformer();
return HolderInstantiator.getHolderInstantiator(
implicitResultTransformer,
queryLocalResultTransformer,

View File

@ -23,15 +23,6 @@
*/
package org.hibernate.ejb;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.CacheRetrieveMode;
import javax.persistence.CacheStoreMode;
import javax.persistence.EntityManager;
@ -60,6 +51,18 @@ import javax.persistence.metamodel.Metamodel;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.logging.Logger;
import org.hibernate.AssertionFailure;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
@ -88,6 +91,7 @@ import org.hibernate.engine.NamedSQLQueryDefinition;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.engine.query.HQLQueryPlan;
import org.hibernate.engine.query.sql.NativeSQLQueryReturn;
import org.hibernate.engine.query.sql.NativeSQLQueryRootReturn;
import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper;
@ -99,10 +103,11 @@ import org.hibernate.engine.transaction.synchronization.spi.ExceptionMapper;
import org.hibernate.engine.transaction.synchronization.spi.ManagedFlushChecker;
import org.hibernate.engine.transaction.synchronization.spi.SynchronizationCallbackCoordinator;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.service.jta.platform.spi.JtaPlatform;
import org.hibernate.transform.BasicTransformerAdapter;
import org.jboss.logging.Logger;
import org.hibernate.type.Type;
/**
* @author <a href="mailto:gavin@hibernate.org">Gavin King</a>
@ -279,18 +284,51 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage
public <T> TypedQuery<T> createQuery(String jpaqlString, Class<T> resultClass) {
try {
// do the translation
org.hibernate.Query hqlQuery = getSession().createQuery( jpaqlString );
if ( hqlQuery.getReturnTypes().length != 1 ) {
throw new IllegalArgumentException( "Cannot create TypedQuery for query with more than one return" );
// do some validation checking
if ( Object[].class.equals( resultClass ) ) {
// no validation needed
}
if ( !resultClass.isAssignableFrom( hqlQuery.getReturnTypes()[0].getReturnedClass() ) ) {
throw new IllegalArgumentException(
"Type specified for TypedQuery [" +
resultClass.getName() +
"] is incompatible with query return type [" +
hqlQuery.getReturnTypes()[0].getReturnedClass() + "]"
);
else if ( Tuple.class.equals( resultClass ) ) {
TupleBuilderTransformer tupleTransformer = new TupleBuilderTransformer( hqlQuery );
hqlQuery.setResultTransformer( tupleTransformer );
}
else {
final HQLQueryPlan queryPlan = unwrap( SessionImplementor.class )
.getFactory()
.getQueryPlanCache()
.getHQLQueryPlan( jpaqlString, false, null );
final Class dynamicInstantiationClass = queryPlan.getDynamicInstantiationResultType();
if ( dynamicInstantiationClass != null ) {
if ( ! resultClass.isAssignableFrom( dynamicInstantiationClass ) ) {
throw new IllegalArgumentException(
"Mismatch in requested result type [" + resultClass.getName() +
"] and actual result type [" + dynamicInstantiationClass.getName() + "]"
);
}
}
else if ( hqlQuery.getReturnTypes().length == 1 ) {
// if we have only a single return expression, its java type should match with the requested type
if ( !resultClass.isAssignableFrom( hqlQuery.getReturnTypes()[0].getReturnedClass() ) ) {
throw new IllegalArgumentException(
"Type specified for TypedQuery [" +
resultClass.getName() +
"] is incompatible with query return type [" +
hqlQuery.getReturnTypes()[0].getReturnedClass() + "]"
);
}
}
else {
throw new IllegalArgumentException(
"Cannot create TypedQuery for query with more than one return using requested result type [" +
resultClass.getName() + "]"
);
}
}
// finally, build/return the query instance
return new QueryImpl<T>( hqlQuery, this );
}
catch ( HibernateException he ) {
@ -298,6 +336,139 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage
}
}
public static class TupleBuilderTransformer extends BasicTransformerAdapter {
private List<TupleElement<?>> tupleElements;
private Map<String,HqlTupleElementImpl> tupleElementsByAlias;
public TupleBuilderTransformer(org.hibernate.Query hqlQuery) {
final Type[] resultTypes = hqlQuery.getReturnTypes();
final int tupleSize = resultTypes.length;
this.tupleElements = CollectionHelper.arrayList( tupleSize );
final String[] aliases = hqlQuery.getReturnAliases();
final boolean hasAliases = aliases != null && aliases.length > 0;
this.tupleElementsByAlias = hasAliases
? CollectionHelper.mapOfSize( tupleSize )
: Collections.<String, HqlTupleElementImpl>emptyMap();
for ( int i = 0; i < tupleSize; i++ ) {
final HqlTupleElementImpl tupleElement = new HqlTupleElementImpl(
i,
aliases == null ? null : aliases[i],
resultTypes[i]
);
tupleElements.add( tupleElement );
if ( hasAliases ) {
final String alias = aliases[i];
if ( alias != null ) {
tupleElementsByAlias.put( alias, tupleElement );
}
}
}
}
@Override
public Object transformTuple(Object[] tuple, String[] aliases) {
if ( tuple.length != tupleElements.size() ) {
throw new IllegalArgumentException(
"Size mismatch between tuple result [" + tuple.length + "] and expected tuple elements [" +
tupleElements.size() + "]"
);
}
return new HqlTupleImpl( tuple );
}
public static class HqlTupleElementImpl<X> implements TupleElement<X> {
private final int position;
private final String alias;
private final Type hibernateType;
public HqlTupleElementImpl(int position, String alias, Type hibernateType) {
this.position = position;
this.alias = alias;
this.hibernateType = hibernateType;
}
@Override
public Class getJavaType() {
return hibernateType.getReturnedClass();
}
@Override
public String getAlias() {
return alias;
}
public int getPosition() {
return position;
}
public Type getHibernateType() {
return hibernateType;
}
}
public class HqlTupleImpl implements Tuple {
private Object[] tuple;
public HqlTupleImpl(Object[] tuple) {
this.tuple = tuple;
}
@Override
public <X> X get(String alias, Class<X> type) {
return (X) get( alias );
}
@Override
public Object get(String alias) {
HqlTupleElementImpl tupleElement = tupleElementsByAlias.get( alias );
if ( tupleElement == null ) {
throw new IllegalArgumentException( "Unknown alias [" + alias + "]" );
}
return tuple[ tupleElement.getPosition() ];
}
@Override
public <X> X get(int i, Class<X> type) {
return (X) get( i );
}
@Override
public Object get(int i) {
if ( i < 0 ) {
throw new IllegalArgumentException( "requested tuple index must be greater than zero" );
}
if ( i > tuple.length ) {
throw new IllegalArgumentException( "requested tuple index exceeds actual tuple size" );
}
return tuple[i];
}
@Override
public Object[] toArray() {
// todo : make a copy?
return tuple;
}
@Override
public List<TupleElement<?>> getElements() {
return tupleElements;
}
@Override
public <X> X get(TupleElement<X> tupleElement) {
if ( HqlTupleElementImpl.class.isInstance( tupleElement ) ) {
return get( ( (HqlTupleElementImpl) tupleElement ).getPosition(), tupleElement.getJavaType() );
}
else {
return get( tupleElement.getAlias(), tupleElement.getJavaType() );
}
}
}
}
public <T> TypedQuery<T> createQuery(
String jpaqlString,
Class<T> resultClass,

View File

@ -26,6 +26,7 @@ package org.hibernate.ejb.test.query;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import javax.persistence.Tuple;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -522,6 +523,33 @@ public class QueryTest extends BaseEntityManagerFunctionalTestCase {
em.close();
}
@Test
public void testTypedScalarQueries() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
Item item = new Item( "Mouse", "Micro$oft mouse" );
em.persist( item );
assertTrue( em.contains( item ) );
em.getTransaction().commit();
em.getTransaction().begin();
Object[] itemData = em.createQuery( "select i.name,i.descr from Item i", Object[].class ).getSingleResult();
assertEquals( 2, itemData.length );
assertEquals( String.class, itemData[0].getClass() );
assertEquals( String.class, itemData[1].getClass() );
Tuple itemTuple = em.createQuery( "select i.name,i.descr from Item i", Tuple.class ).getSingleResult();
assertEquals( 2, itemTuple.getElements().size() );
assertEquals( String.class, itemTuple.get( 0 ).getClass() );
assertEquals( String.class, itemTuple.get( 1 ).getClass() );
Item itemView = em.createQuery( "select new Item(i.name,i.descr) from Item i", Item.class ).getSingleResult();
assertNotNull( itemView );
assertEquals( "Micro$oft mouse", itemView.getDescr() );
em.remove( item );
em.getTransaction().commit();
em.close();
}
@Override
public Class[] getAnnotatedClasses() {
return new Class[]{