diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AbstractPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AbstractPropertyHolder.java index de8f350666..1ce98cc7fb 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AbstractPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AbstractPropertyHolder.java @@ -45,6 +45,7 @@ import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.type.PrimitiveWrapperHelper; /** * No idea. @@ -141,50 +142,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder { } return converterDefinedType.equals( propertyType ) - || arePrimitiveWrapperEquivalents( converterDefinedType, propertyType ); - } - - private boolean arePrimitiveWrapperEquivalents(Class converterDefinedType, Class propertyType) { - if ( converterDefinedType.isPrimitive() ) { - return getWrapperEquivalent( converterDefinedType ).equals( propertyType ); - } - else if ( propertyType.isPrimitive() ) { - return getWrapperEquivalent( propertyType ).equals( converterDefinedType ); - } - return false; - } - - private static Class getWrapperEquivalent(Class primitive) { - if ( ! primitive.isPrimitive() ) { - throw new AssertionFailure( "Passed type for which to locate wrapper equivalent was not a primitive" ); - } - - if ( boolean.class.equals( primitive ) ) { - return Boolean.class; - } - else if ( char.class.equals( primitive ) ) { - return Character.class; - } - else if ( byte.class.equals( primitive ) ) { - return Byte.class; - } - else if ( short.class.equals( primitive ) ) { - return Short.class; - } - else if ( int.class.equals( primitive ) ) { - return Integer.class; - } - else if ( long.class.equals( primitive ) ) { - return Long.class; - } - else if ( float.class.equals( primitive ) ) { - return Float.class; - } - else if ( double.class.equals( primitive ) ) { - return Double.class; - } - - throw new AssertionFailure( "Unexpected primitive type (VOID most likely) passed to getWrapperEquivalent" ); + || PrimitiveWrapperHelper.arePrimitiveWrapperEquivalents( converterDefinedType, propertyType ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/type/PrimitiveWrapperHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/type/PrimitiveWrapperHelper.java index f8a9e73cf5..89a5ff3579 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/type/PrimitiveWrapperHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/type/PrimitiveWrapperHelper.java @@ -211,7 +211,10 @@ public class PrimitiveWrapperHelper { return (PrimitiveWrapperDescriptor) DoubleDescriptor.INSTANCE; } - // most likely void.class, which we can't really handle here + if ( void.class == primitiveClazz ) { + throw new IllegalArgumentException( "void, as primitive type, has no wrapper equivalent" ); + } + throw new IllegalArgumentException( "Unrecognized primitive type class : " + primitiveClazz.getName() ); } @@ -266,4 +269,14 @@ public class PrimitiveWrapperHelper { return false; } } + + public static boolean arePrimitiveWrapperEquivalents(Class converterDefinedType, Class propertyType) { + if ( converterDefinedType.isPrimitive() ) { + return getDescriptorByPrimitiveType( converterDefinedType ).getWrapperClass().equals( propertyType ); + } + else if ( propertyType.isPrimitive() ) { + return getDescriptorByPrimitiveType( propertyType ).getWrapperClass().equals( converterDefinedType ); + } + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/CollectionFetchReturn.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/CollectionFetchReturn.java index 274c2e95eb..21cfeab08f 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/CollectionFetchReturn.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/CollectionFetchReturn.java @@ -22,13 +22,13 @@ * Boston, MA 02110-1301 USA * */ -package org.hibernate.loader.custom; +package org.hibernate.loader.custom; import org.hibernate.LockMode; import org.hibernate.loader.CollectionAliases; import org.hibernate.loader.EntityAliases; /** - * Spefically a fetch return that refers to a collection association. + * Specifically a fetch return that refers to a collection association. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/ConstructorResultColumnProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/ConstructorResultColumnProcessor.java new file mode 100644 index 0000000000..0d315f9ac4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/ConstructorResultColumnProcessor.java @@ -0,0 +1,124 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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 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 + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.loader.custom; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.internal.util.type.PrimitiveWrapperHelper; +import org.hibernate.type.Type; + +/** + * Represents a {@link javax.persistence.ConstructorResult} within the custom query. + * + * @author Steve Ebersole + */ +public class ConstructorResultColumnProcessor implements ResultColumnProcessor { + private final Class targetClass; + private final ScalarResultColumnProcessor[] scalarProcessors; + + private Constructor constructor; + + public ConstructorResultColumnProcessor(Class targetClass, ScalarResultColumnProcessor[] scalarProcessors) { + this.targetClass = targetClass; + this.scalarProcessors = scalarProcessors; + } + + @Override + public void performDiscovery(JdbcResultMetadata metadata, List types, List aliases) throws SQLException { + final List localTypes = new ArrayList(); + for ( ScalarResultColumnProcessor scalar : scalarProcessors ) { + scalar.performDiscovery( metadata, localTypes, aliases ); + } + + types.addAll( localTypes ); + + constructor = resolveConstructor( targetClass, localTypes ); + } + + @Override + public Object extract(Object[] data, ResultSet resultSet, SessionImplementor session) + throws SQLException, HibernateException { + if ( constructor == null ) { + throw new IllegalStateException( "Constructor to call was null" ); + } + + final Object[] args = new Object[ scalarProcessors.length ]; + for ( int i = 0; i < scalarProcessors.length; i++ ) { + args[i] = scalarProcessors[i].extract( data, resultSet, session ); + } + + try { + return constructor.newInstance( args ); + } + catch (InvocationTargetException e) { + throw new HibernateException( + String.format( "Unable to call %s constructor", constructor.getDeclaringClass() ), + e + ); + } + catch (Exception e) { + throw new HibernateException( + String.format( "Unable to call %s constructor", constructor.getDeclaringClass() ), + e + ); + } + } + + private static Constructor resolveConstructor(Class targetClass, List types) { + for ( Constructor constructor : targetClass.getConstructors() ) { + final Class[] argumentTypes = constructor.getParameterTypes(); + if ( argumentTypes.length != types.size() ) { + continue; + } + + boolean allMatched = true; + for ( int i = 0; i < argumentTypes.length; i++ ) { + if ( ! areAssignmentCompatible( argumentTypes[i], types.get( i ).getReturnedClass() ) ) { + allMatched = false; + break; + } + } + if ( !allMatched ) { + continue; + } + + return constructor; + } + + throw new IllegalArgumentException( "Could not locate appropriate constructor on class : " + targetClass.getName() ); + } + + @SuppressWarnings("unchecked") + private static boolean areAssignmentCompatible(Class argumentType, Class typeReturnedClass) { + return argumentType.isAssignableFrom( typeReturnedClass ) + || PrimitiveWrapperHelper.arePrimitiveWrapperEquivalents( argumentType, typeReturnedClass ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/ConstructorReturn.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/ConstructorReturn.java index c41ac0b516..d5b1168304 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/ConstructorReturn.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/ConstructorReturn.java @@ -23,52 +23,18 @@ */ package org.hibernate.loader.custom; -import java.lang.reflect.Constructor; - /** + * A return representing a {@link javax.persistence.ConstructorResult} + * * @author Steve Ebersole */ public class ConstructorReturn implements Return { private final Class targetClass; private final ScalarReturn[] scalars; -// private final Constructor constructor; - public ConstructorReturn(Class targetClass, ScalarReturn[] scalars) { this.targetClass = targetClass; this.scalars = scalars; - -// constructor = resolveConstructor( targetClass, scalars ); - } - - private static Constructor resolveConstructor(Class targetClass, ScalarReturn[] scalars) { - for ( Constructor constructor : targetClass.getConstructors() ) { - final Class[] argumentTypes = constructor.getParameterTypes(); - if ( argumentTypes.length != scalars.length ) { - continue; - } - - boolean allMatched = true; - for ( int i = 0; i < argumentTypes.length; i++ ) { - if ( areAssignmentCompatible( argumentTypes[i], scalars[i].getType().getReturnedClass() ) ) { - allMatched = false; - break; - } - } - if ( !allMatched ) { - continue; - } - - return constructor; - } - - throw new IllegalArgumentException( "Could not locate appropriate constructor on class : " + targetClass.getName() ); - } - - @SuppressWarnings("unchecked") - private static boolean areAssignmentCompatible(Class argumentType, Class typeReturnedClass) { - // todo : add handling for primitive/wrapper equivalents - return argumentType.isAssignableFrom( typeReturnedClass ); } public Class getTargetClass() { @@ -78,8 +44,4 @@ public class ConstructorReturn implements Return { public ScalarReturn[] getScalars() { return scalars; } - -// public Constructor getConstructor() { -// return constructor; -// } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomLoader.java index 5196db486a..25a6220073 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomLoader.java @@ -24,14 +24,10 @@ package org.hibernate.loader.custom; import java.io.Serializable; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.sql.ResultSet; -import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -107,29 +103,28 @@ public class CustomLoader extends Loader { this.querySpaces.addAll( customQuery.getQuerySpaces() ); this.namedParameterBindPoints = customQuery.getNamedParameterBindPoints(); - List entityPersisters = new ArrayList(); - List entityOwners = new ArrayList(); - List entityAliases = new ArrayList(); + List entityPersisters = new ArrayList(); + List entityOwners = new ArrayList(); + List entityAliases = new ArrayList(); - List collectionPersisters = new ArrayList(); - List collectionOwners = new ArrayList(); - List collectionAliases = new ArrayList(); + List collectionPersisters = new ArrayList(); + List collectionOwners = new ArrayList(); + List collectionAliases = new ArrayList(); + + List lockModes = new ArrayList(); + List resultColumnProcessors = new ArrayList(); + List nonScalarReturnList = new ArrayList(); + List resultTypes = new ArrayList(); + List specifiedAliases = new ArrayList(); - List lockModes = new ArrayList(); - List resultColumnProcessors = new ArrayList(); - List nonScalarReturnList = new ArrayList(); - List resultTypes = new ArrayList(); - List specifiedAliases = new ArrayList(); int returnableCounter = 0; boolean hasScalars = false; - List includeInResultRowList = new ArrayList(); + List includeInResultRowList = new ArrayList(); - Iterator itr = customQuery.getCustomQueryReturns().iterator(); - while ( itr.hasNext() ) { - final Return rtn = ( Return ) itr.next(); + for ( Return rtn : customQuery.getCustomQueryReturns() ) { if ( rtn instanceof ScalarReturn ) { - ScalarReturn scalarRtn = ( ScalarReturn ) rtn; + ScalarReturn scalarRtn = (ScalarReturn) rtn; resultTypes.add( scalarRtn.getType() ); specifiedAliases.add( scalarRtn.getColumnAlias() ); resultColumnProcessors.add( @@ -147,7 +142,7 @@ public class CustomLoader extends Loader { includeInResultRowList.add( true ); hasScalars = true; - ScalarResultColumnProcessor[] scalarProcessors = new ScalarResultColumnProcessor[ constructorReturn.getScalars().length ]; + ScalarResultColumnProcessor[] scalarProcessors = new ScalarResultColumnProcessor[constructorReturn.getScalars().length]; int i = 0; for ( ScalarReturn scalarReturn : constructorReturn.getScalars() ) { scalarProcessors[i++] = new ScalarResultColumnProcessor( @@ -161,8 +156,8 @@ public class CustomLoader extends Loader { ); } else if ( rtn instanceof RootReturn ) { - RootReturn rootRtn = ( RootReturn ) rtn; - Queryable persister = ( Queryable ) factory.getEntityPersister( rootRtn.getEntityName() ); + RootReturn rootRtn = (RootReturn) rtn; + Queryable persister = (Queryable) factory.getEntityPersister( rootRtn.getEntityName() ); entityPersisters.add( persister ); lockModes.add( (rootRtn.getLockMode()) ); resultColumnProcessors.add( new NonScalarResultColumnProcessor( returnableCounter++ ) ); @@ -175,9 +170,9 @@ public class CustomLoader extends Loader { includeInResultRowList.add( true ); } else if ( rtn instanceof CollectionReturn ) { - CollectionReturn collRtn = ( CollectionReturn ) rtn; + CollectionReturn collRtn = (CollectionReturn) rtn; String role = collRtn.getOwnerEntityName() + "." + collRtn.getOwnerProperty(); - QueryableCollection persister = ( QueryableCollection ) factory.getCollectionPersister( role ); + QueryableCollection persister = (QueryableCollection) factory.getCollectionPersister( role ); collectionPersisters.add( persister ); lockModes.add( collRtn.getLockMode() ); resultColumnProcessors.add( new NonScalarResultColumnProcessor( returnableCounter++ ) ); @@ -189,7 +184,7 @@ public class CustomLoader extends Loader { // determine if the collection elements are entities... Type elementType = persister.getElementType(); if ( elementType.isEntityType() ) { - Queryable elementPersister = ( Queryable ) ( ( EntityType ) elementType ).getAssociatedJoinable( factory ); + Queryable elementPersister = (Queryable) ((EntityType) elementType).getAssociatedJoinable( factory ); entityPersisters.add( elementPersister ); entityOwners.add( -1 ); entityAliases.add( collRtn.getElementEntityAliases() ); @@ -198,15 +193,15 @@ public class CustomLoader extends Loader { includeInResultRowList.add( true ); } else if ( rtn instanceof EntityFetchReturn ) { - EntityFetchReturn fetchRtn = ( EntityFetchReturn ) rtn; + EntityFetchReturn fetchRtn = (EntityFetchReturn) rtn; NonScalarReturn ownerDescriptor = fetchRtn.getOwner(); int ownerIndex = nonScalarReturnList.indexOf( ownerDescriptor ); entityOwners.add( ownerIndex ); lockModes.add( fetchRtn.getLockMode() ); Queryable ownerPersister = determineAppropriateOwnerPersister( ownerDescriptor ); - EntityType fetchedType = ( EntityType ) ownerPersister.getPropertyType( fetchRtn.getOwnerProperty() ); + EntityType fetchedType = (EntityType) ownerPersister.getPropertyType( fetchRtn.getOwnerProperty() ); String entityName = fetchedType.getAssociatedEntityName( getFactory() ); - Queryable persister = ( Queryable ) factory.getEntityPersister( entityName ); + Queryable persister = (Queryable) factory.getEntityPersister( entityName ); entityPersisters.add( persister ); nonScalarReturnList.add( rtn ); specifiedAliases.add( fetchRtn.getAlias() ); @@ -215,14 +210,14 @@ public class CustomLoader extends Loader { includeInResultRowList.add( false ); } else if ( rtn instanceof CollectionFetchReturn ) { - CollectionFetchReturn fetchRtn = ( CollectionFetchReturn ) rtn; + CollectionFetchReturn fetchRtn = (CollectionFetchReturn) rtn; NonScalarReturn ownerDescriptor = fetchRtn.getOwner(); int ownerIndex = nonScalarReturnList.indexOf( ownerDescriptor ); collectionOwners.add( ownerIndex ); lockModes.add( fetchRtn.getLockMode() ); Queryable ownerPersister = determineAppropriateOwnerPersister( ownerDescriptor ); String role = ownerPersister.getEntityName() + '.' + fetchRtn.getOwnerProperty(); - QueryableCollection persister = ( QueryableCollection ) factory.getCollectionPersister( role ); + QueryableCollection persister = (QueryableCollection) factory.getCollectionPersister( role ); collectionPersisters.add( persister ); nonScalarReturnList.add( rtn ); specifiedAliases.add( fetchRtn.getAlias() ); @@ -230,7 +225,7 @@ public class CustomLoader extends Loader { // determine if the collection elements are entities... Type elementType = persister.getElementType(); if ( elementType.isEntityType() ) { - Queryable elementPersister = ( Queryable ) ( ( EntityType ) elementType ).getAssociatedJoinable( factory ); + Queryable elementPersister = (Queryable) ((EntityType) elementType).getAssociatedJoinable( factory ); entityPersisters.add( elementPersister ); entityOwners.add( ownerIndex ); entityAliases.add( fetchRtn.getElementEntityAliases() ); @@ -245,27 +240,27 @@ public class CustomLoader extends Loader { this.entityPersisters = new Queryable[ entityPersisters.size() ]; for ( int i = 0; i < entityPersisters.size(); i++ ) { - this.entityPersisters[i] = ( Queryable ) entityPersisters.get( i ); + this.entityPersisters[i] = entityPersisters.get( i ); } this.entiytOwners = ArrayHelper.toIntArray( entityOwners ); this.entityAliases = new EntityAliases[ entityAliases.size() ]; for ( int i = 0; i < entityAliases.size(); i++ ) { - this.entityAliases[i] = ( EntityAliases ) entityAliases.get( i ); + this.entityAliases[i] = entityAliases.get( i ); } this.collectionPersisters = new QueryableCollection[ collectionPersisters.size() ]; for ( int i = 0; i < collectionPersisters.size(); i++ ) { - this.collectionPersisters[i] = ( QueryableCollection ) collectionPersisters.get( i ); + this.collectionPersisters[i] = collectionPersisters.get( i ); } this.collectionOwners = ArrayHelper.toIntArray( collectionOwners ); this.collectionAliases = new CollectionAliases[ collectionAliases.size() ]; for ( int i = 0; i < collectionAliases.size(); i++ ) { - this.collectionAliases[i] = ( CollectionAliases ) collectionAliases.get( i ); + this.collectionAliases[i] = collectionAliases.get( i ); } this.lockModes = new LockMode[ lockModes.size() ]; for ( int i = 0; i < lockModes.size(); i++ ) { - this.lockModes[i] = ( LockMode ) lockModes.get( i ); + this.lockModes[i] = lockModes.get( i ); } this.resultTypes = ArrayHelper.toTypeArray( resultTypes ); @@ -273,7 +268,7 @@ public class CustomLoader extends Loader { this.rowProcessor = new ResultRowProcessor( hasScalars, - ( ResultColumnProcessor[] ) resultColumnProcessors.toArray( new ResultColumnProcessor[ resultColumnProcessors.size() ] ) + resultColumnProcessors.toArray( new ResultColumnProcessor[ resultColumnProcessors.size() ] ) ); this.includeInResultRow = ArrayHelper.toBooleanArray( includeInResultRowList ); @@ -385,9 +380,8 @@ public class CustomLoader extends Loader { return sql; } - public ScrollableResults scroll( - final QueryParameters queryParameters, - final SessionImplementor session) throws HibernateException { + public ScrollableResults scroll(final QueryParameters queryParameters, final SessionImplementor session) + throws HibernateException { return scroll( queryParameters, resultTypes, @@ -436,6 +430,7 @@ public class CustomLoader extends Loader { } @Override + @SuppressWarnings("unchecked") protected List getResultList(List results, ResultTransformer resultTransformer) throws QueryException { // meant to handle dynamic instantiation queries...(Copy from QueryLoader) HolderInstantiator holderInstantiator = HolderInstantiator.getHolderInstantiator( @@ -489,233 +484,16 @@ public class CustomLoader extends Loader { } - public class ResultRowProcessor { - private final boolean hasScalars; - private ResultColumnProcessor[] columnProcessors; - - public ResultRowProcessor(boolean hasScalars, ResultColumnProcessor[] columnProcessors) { - this.hasScalars = hasScalars || ( columnProcessors == null || columnProcessors.length == 0 ); - this.columnProcessors = columnProcessors; - } - - public void prepareForAutoDiscovery(Metadata metadata) throws SQLException { - if ( columnProcessors == null || columnProcessors.length == 0 ) { - int columns = metadata.getColumnCount(); - columnProcessors = new ResultColumnProcessor[ columns ]; - for ( int i = 1; i <= columns; i++ ) { - columnProcessors[ i - 1 ] = new ScalarResultColumnProcessor( i ); - } - - } - } - - /** - * Build a logical result row. - *

- * At this point, Loader has already processed all non-scalar result data. We - * just need to account for scalar result data here... - * - * @param data Entity data defined as "root returns" and already handled by the - * normal Loader mechanism. - * @param resultSet The JDBC result set (positioned at the row currently being processed). - * @param hasTransformer Does this query have an associated {@link ResultTransformer} - * @param session The session from which the query request originated. - * @return The logical result row - * @throws SQLException - * @throws HibernateException - */ - public Object buildResultRow( - Object[] data, - ResultSet resultSet, - boolean hasTransformer, - SessionImplementor session) throws SQLException, HibernateException { - Object[] resultRow = buildResultRow( data, resultSet, session ); - return ( hasTransformer ) - ? resultRow - : ( resultRow.length == 1 ) - ? resultRow[0] - : resultRow; - } - public Object[] buildResultRow( - Object[] data, - ResultSet resultSet, - SessionImplementor session) throws SQLException, HibernateException { - Object[] resultRow; - if ( !hasScalars ) { - resultRow = data; - } - else { - // build an array with indices equal to the total number - // of actual returns in the result Hibernate will return - // for this query (scalars + non-scalars) - resultRow = new Object[ columnProcessors.length ]; - for ( int i = 0; i < columnProcessors.length; i++ ) { - resultRow[i] = columnProcessors[i].extract( data, resultSet, session ); - } - } - - return resultRow; - } - } - - private static interface ResultColumnProcessor { - public Object extract(Object[] data, ResultSet resultSet, SessionImplementor session) throws SQLException, HibernateException; - public void performDiscovery(Metadata metadata, List types, List aliases) throws SQLException, HibernateException; - } - - public class NonScalarResultColumnProcessor implements ResultColumnProcessor { - private final int position; - - public NonScalarResultColumnProcessor(int position) { - this.position = position; - } - - @Override - public Object extract( - Object[] data, - ResultSet resultSet, - SessionImplementor session) throws SQLException, HibernateException { - return data[ position ]; - } - - @Override - public void performDiscovery(Metadata metadata, List types, List aliases) { - } - - } - - public class ScalarResultColumnProcessor implements ResultColumnProcessor { - private int position = -1; - private String alias; - private Type type; - - public ScalarResultColumnProcessor(int position) { - this.position = position; - } - - public ScalarResultColumnProcessor(String alias, Type type) { - this.alias = alias; - this.type = type; - } - - @Override - public Object extract( - Object[] data, - ResultSet resultSet, - SessionImplementor session) throws SQLException, HibernateException { - return type.nullSafeGet( resultSet, alias, session, null ); - } - - @Override - public void performDiscovery(Metadata metadata, List types, List aliases) throws SQLException { - if ( alias == null ) { - alias = metadata.getColumnName( position ); - } - else if ( position < 0 ) { - position = metadata.resolveColumnPosition( alias ); - } - if ( type == null ) { - type = metadata.getHibernateType( position ); - } - types.add( type ); - aliases.add( alias ); - } - } - - public class ConstructorResultColumnProcessor implements ResultColumnProcessor { - private final Class targetClass; - private final ScalarResultColumnProcessor[] scalarProcessors; - - private Constructor constructor; - - public ConstructorResultColumnProcessor(Class targetClass, ScalarResultColumnProcessor[] scalarProcessors) { - this.targetClass = targetClass; - this.scalarProcessors = scalarProcessors; - } - - @Override - public Object extract(Object[] data, ResultSet resultSet, SessionImplementor session) - throws SQLException, HibernateException { - if ( constructor == null ) { - throw new IllegalStateException( "Constructor to call was null" ); - } - - final Object[] args = new Object[ scalarProcessors.length ]; - for ( int i = 0; i < scalarProcessors.length; i++ ) { - args[i] = scalarProcessors[i].extract( data, resultSet, session ); - } - - try { - return constructor.newInstance( args ); - } - catch (InvocationTargetException e) { - throw new HibernateException( - String.format( "Unable to call %s constructor", constructor.getDeclaringClass() ), - e - ); - } - catch (Exception e) { - throw new HibernateException( - String.format( "Unable to call %s constructor", constructor.getDeclaringClass() ), - e - ); - } - } - - @Override - public void performDiscovery(Metadata metadata, List types, List aliases) throws SQLException { - final List localTypes = new ArrayList(); - for ( ScalarResultColumnProcessor scalar : scalarProcessors ) { - scalar.performDiscovery( metadata, localTypes, aliases ); - } - - types.addAll( localTypes ); - - constructor = resolveConstructor( targetClass, localTypes ); - } - - } - - private static Constructor resolveConstructor(Class targetClass, List types) { - for ( Constructor constructor : targetClass.getConstructors() ) { - final Class[] argumentTypes = constructor.getParameterTypes(); - if ( argumentTypes.length != types.size() ) { - continue; - } - - boolean allMatched = true; - for ( int i = 0; i < argumentTypes.length; i++ ) { - if ( ! areAssignmentCompatible( argumentTypes[i], types.get( i ).getReturnedClass() ) ) { - allMatched = false; - break; - } - } - if ( !allMatched ) { - continue; - } - - return constructor; - } - - throw new IllegalArgumentException( "Could not locate appropriate constructor on class : " + targetClass.getName() ); - } - - @SuppressWarnings("unchecked") - private static boolean areAssignmentCompatible(Class argumentType, Class typeReturnedClass) { - // todo : add handling for primitive/wrapper equivalents - return argumentType.isAssignableFrom( typeReturnedClass ); - } - @Override protected void autoDiscoverTypes(ResultSet rs) { try { - Metadata metadata = new Metadata( getFactory(), rs ); + JdbcResultMetadata metadata = new JdbcResultMetadata( getFactory(), rs ); rowProcessor.prepareForAutoDiscovery( metadata ); List aliases = new ArrayList(); List types = new ArrayList(); - for ( int i = 0; i < rowProcessor.columnProcessors.length; i++ ) { - rowProcessor.columnProcessors[i].performDiscovery( metadata, types, aliases ); + for ( ResultColumnProcessor resultProcessor : rowProcessor.getColumnProcessors() ) { + resultProcessor.performDiscovery( metadata, types, aliases ); } validateAliases( aliases ); @@ -742,75 +520,14 @@ public class CustomLoader extends Loader { boolean alreadyExisted = !aliasesSet.add( alias ); if ( alreadyExisted ) { throw new NonUniqueDiscoveredSqlAliasException( - "Encountered a duplicated sql alias [" + alias + - "] during auto-discovery of a native-sql query" + "Encountered a duplicated sql alias [" + alias + "] during auto-discovery of a native-sql query" ); } } } + @SuppressWarnings("UnusedParameters") protected void validateAlias(String alias) { } - private static class Metadata { - private final SessionFactoryImplementor factory; - private final ResultSet resultSet; - private final ResultSetMetaData resultSetMetaData; - - public Metadata(SessionFactoryImplementor factory, ResultSet resultSet) throws HibernateException { - try { - this.factory = factory; - this.resultSet = resultSet; - this.resultSetMetaData = resultSet.getMetaData(); - } - catch( SQLException e ) { - throw new HibernateException( "Could not extract result set metadata", e ); - } - } - - public int getColumnCount() throws HibernateException { - try { - return resultSetMetaData.getColumnCount(); - } - catch( SQLException e ) { - throw new HibernateException( "Could not determine result set column count", e ); - } - } - - public int resolveColumnPosition(String columnName) throws HibernateException { - try { - return resultSet.findColumn( columnName ); - } - catch( SQLException e ) { - throw new HibernateException( "Could not resolve column name in result set [" + columnName + "]", e ); - } - } - - public String getColumnName(int position) throws HibernateException { - try { - return factory.getDialect().getColumnAliasExtractor().extractColumnAlias( resultSetMetaData, position ); - } - catch( SQLException e ) { - throw new HibernateException( "Could not resolve column name [" + position + "]", e ); - } - } - - public Type getHibernateType(int columnPos) throws SQLException { - int columnType = resultSetMetaData.getColumnType( columnPos ); - int scale = resultSetMetaData.getScale( columnPos ); - int precision = resultSetMetaData.getPrecision( columnPos ); - int length = precision; - if ( columnType == 1 && precision == 0 ) { - length = resultSetMetaData.getColumnDisplaySize( columnPos ); - } - return factory.getTypeResolver().heuristicType( - factory.getDialect().getHibernateTypeName( - columnType, - length, - precision, - scale - ) - ); - } - } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/JdbcResultMetadata.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/JdbcResultMetadata.java new file mode 100644 index 0000000000..deb9aecdbe --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/JdbcResultMetadata.java @@ -0,0 +1,102 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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 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 + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.loader.custom; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Types; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.type.Type; + +/** + * Simplified access to JDBC ResultSetMetaData + * + * @author Steve Ebersole + */ +class JdbcResultMetadata { + private final SessionFactoryImplementor factory; + private final ResultSet resultSet; + private final ResultSetMetaData resultSetMetaData; + + public JdbcResultMetadata(SessionFactoryImplementor factory, ResultSet resultSet) throws HibernateException { + try { + this.factory = factory; + this.resultSet = resultSet; + this.resultSetMetaData = resultSet.getMetaData(); + } + catch( SQLException e ) { + throw new HibernateException( "Could not extract result set metadata", e ); + } + } + + public int getColumnCount() throws HibernateException { + try { + return resultSetMetaData.getColumnCount(); + } + catch( SQLException e ) { + throw new HibernateException( "Could not determine result set column count", e ); + } + } + + public int resolveColumnPosition(String columnName) throws HibernateException { + try { + return resultSet.findColumn( columnName ); + } + catch( SQLException e ) { + throw new HibernateException( "Could not resolve column name in result set [" + columnName + "]", e ); + } + } + + public String getColumnName(int position) throws HibernateException { + try { + return factory.getDialect().getColumnAliasExtractor().extractColumnAlias( resultSetMetaData, position ); + } + catch( SQLException e ) { + throw new HibernateException( "Could not resolve column name [" + position + "]", e ); + } + } + + public Type getHibernateType(int columnPos) throws SQLException { + int columnType = resultSetMetaData.getColumnType( columnPos ); + int scale = resultSetMetaData.getScale( columnPos ); + int precision = resultSetMetaData.getPrecision( columnPos ); + + int length = precision; + if ( columnType == Types.CHAR && precision == 0 ) { + length = resultSetMetaData.getColumnDisplaySize( columnPos ); + } + + return factory.getTypeResolver().heuristicType( + factory.getDialect().getHibernateTypeName( + columnType, + length, + precision, + scale + ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/NonScalarResultColumnProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/NonScalarResultColumnProcessor.java new file mode 100644 index 0000000000..2e5790f1d5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/NonScalarResultColumnProcessor.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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 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 + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.loader.custom; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.type.Type; + +/** + * Represents non-scalar returns within the custom query. Most of the heavy lifting for non-scalar results + * is done within Loader itself. + * + * @author Steve Ebersole + */ +public class NonScalarResultColumnProcessor implements ResultColumnProcessor { + private final int position; + + public NonScalarResultColumnProcessor(int position) { + this.position = position; + } + + @Override + public void performDiscovery(JdbcResultMetadata metadata, List types, List aliases) { + // nothing to discover for non-scalar results + } + + @Override + public Object extract(Object[] data, ResultSet resultSet, SessionImplementor session) + throws SQLException, HibernateException { + return data[ position ]; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/ResultColumnProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/ResultColumnProcessor.java new file mode 100644 index 0000000000..277be171f6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/ResultColumnProcessor.java @@ -0,0 +1,67 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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 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 + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.loader.custom; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.type.Type; + +/** Processor for each "column" in a custom query result. May map to more than one physical column in the JDBC ResultSert. + * + * @author Steve Ebersole + */ +interface ResultColumnProcessor { + /** + * Perform discovery, if needed. Typically discovery activities include looking up the column name in the + * ResultSet or JDBC type codes. + * + * @param metadata Delegate for accessing metadata about the JDBC ResultSet + * @param types The building List of types + * @param aliases The building list of column names/aliases + * + * @throws SQLException Indicates a problem accessing the JDBC objects + * @throws HibernateException Indicates a higher-level problem already categorized by Hibernate + */ + public void performDiscovery(JdbcResultMetadata metadata, List types, List aliases) + throws SQLException, HibernateException; + + /** + * Perform The extraction + * + * @param data All non-scalar results (handled at a higher level than these processors) + * @param resultSet The JDBC result set. + * @param session The Hibernate Session + * + * @return The extracted value + * + * @throws SQLException Indicates a problem accessing the JDBC objects + * @throws HibernateException Indicates a higher-level problem already categorized by Hibernate + */ + public Object extract(Object[] data, ResultSet resultSet, SessionImplementor session) + throws SQLException, HibernateException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/ResultRowProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/ResultRowProcessor.java new file mode 100644 index 0000000000..32a3316e0e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/ResultRowProcessor.java @@ -0,0 +1,106 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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 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 + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.loader.custom; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; + +/** + * Models an entire "row" of results within a custom query + * + * @author Steve Ebersole + */ +public class ResultRowProcessor { + private final boolean hasScalars; + private ResultColumnProcessor[] columnProcessors; + + public ResultRowProcessor(boolean hasScalars, ResultColumnProcessor[] columnProcessors) { + this.hasScalars = hasScalars || ( columnProcessors == null || columnProcessors.length == 0 ); + this.columnProcessors = columnProcessors; + } + + public ResultColumnProcessor[] getColumnProcessors() { + return columnProcessors; + } + + public void prepareForAutoDiscovery(JdbcResultMetadata metadata) throws SQLException { + if ( columnProcessors == null || columnProcessors.length == 0 ) { + int columns = metadata.getColumnCount(); + columnProcessors = new ResultColumnProcessor[ columns ]; + for ( int i = 1; i <= columns; i++ ) { + columnProcessors[ i - 1 ] = new ScalarResultColumnProcessor( i ); + } + } + } + + /** + * Build a logical result row. + *

+ * At this point, Loader has already processed all non-scalar result data. We + * just need to account for scalar result data here... + * + * @param data Entity data defined as "root returns" and already handled by the + * normal Loader mechanism. + * @param resultSet The JDBC result set (positioned at the row currently being processed). + * @param hasTransformer Does this query have an associated {@link org.hibernate.transform.ResultTransformer} + * @param session The session from which the query request originated. + * @return The logical result row + * @throws java.sql.SQLException + * @throws org.hibernate.HibernateException + */ + public Object buildResultRow(Object[] data, ResultSet resultSet, boolean hasTransformer, SessionImplementor session) + throws SQLException, HibernateException { + final Object[] resultRow = buildResultRow( data, resultSet, session ); + if ( hasTransformer ) { + return resultRow; + } + else { + return resultRow.length == 1 + ? resultRow[0] + : resultRow; + } + } + + public Object[] buildResultRow(Object[] data, ResultSet resultSet, SessionImplementor session) + throws SQLException, HibernateException { + Object[] resultRow; + if ( !hasScalars ) { + resultRow = data; + } + else { + // build an array with indices equal to the total number + // of actual returns in the result Hibernate will return + // for this query (scalars + non-scalars) + resultRow = new Object[ columnProcessors.length ]; + for ( int i = 0; i < columnProcessors.length; i++ ) { + resultRow[i] = columnProcessors[i].extract( data, resultSet, session ); + } + } + + return resultRow; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/ScalarResultColumnProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/ScalarResultColumnProcessor.java new file mode 100644 index 0000000000..8f4e0eff74 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/ScalarResultColumnProcessor.java @@ -0,0 +1,73 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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 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 + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.loader.custom; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.type.Type; + +/** + * Represents a scalar result within the custom query + * + * @author Steve Ebersole + */ +public class ScalarResultColumnProcessor implements ResultColumnProcessor { + private int position = -1; + private String alias; + private Type type; + + public ScalarResultColumnProcessor(int position) { + this.position = position; + } + + public ScalarResultColumnProcessor(String alias, Type type) { + this.alias = alias; + this.type = type; + } + + @Override + public void performDiscovery(JdbcResultMetadata metadata, List types, List aliases) throws SQLException { + if ( alias == null ) { + alias = metadata.getColumnName( position ); + } + else if ( position < 0 ) { + position = metadata.resolveColumnPosition( alias ); + } + if ( type == null ) { + type = metadata.getHibernateType( position ); + } + types.add( type ); + aliases.add( alias ); + } + + @Override + public Object extract(Object[] data, ResultSet resultSet, SessionImplementor session) + throws SQLException, HibernateException { + return type.nullSafeGet( resultSet, alias, session, null ); + } +} diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/query/ConstructorResultNativeQueryTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/query/ConstructorResultNativeQueryTest.java index 5bdab5f479..59e651827f 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/query/ConstructorResultNativeQueryTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/query/ConstructorResultNativeQueryTest.java @@ -29,8 +29,10 @@ import javax.persistence.ConstructorResult; import javax.persistence.Entity; import javax.persistence.EntityManager; import javax.persistence.Id; +import javax.persistence.NamedNativeQueries; import javax.persistence.NamedNativeQuery; import javax.persistence.SqlResultSetMapping; +import javax.persistence.SqlResultSetMappings; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; @@ -52,22 +54,54 @@ import static org.junit.Assert.assertEquals; */ public class ConstructorResultNativeQueryTest extends BaseEntityManagerFunctionalTestCase { @Entity( name = "Person" ) - @SqlResultSetMapping( - name = "person-id-and-name", - classes = { - @ConstructorResult( - targetClass = Person.class, - columns = { - @ColumnResult( name = "id" ), - @ColumnResult( name = "p_name" ) + @SqlResultSetMappings( + value = { + @SqlResultSetMapping( + name = "person-id-and-name", + classes = { + @ConstructorResult( + targetClass = Person.class, + columns = { + @ColumnResult( name = "id" ), + @ColumnResult( name = "p_name" ) + } + ) + } + ), + @SqlResultSetMapping( + name = "person-id-and-name2", + classes = { + @ConstructorResult( + targetClass = Person.class, + columns = { + @ColumnResult( name = "id" ), + @ColumnResult( name = "p_name" ) + } + ), + @ConstructorResult( + targetClass = Person.class, + columns = { + @ColumnResult( name = "id2" ), + @ColumnResult( name = "p_name2" ) + } + ) } ) } ) - @NamedNativeQuery( - name = "person-id-and-name", - query = "select p.id, p.p_name from person p order by p.p_name", - resultSetMapping = "person-id-and-name" + @NamedNativeQueries( + value = { + @NamedNativeQuery( + name = "person-id-and-name", + query = "select p.id, p.p_name from person p order by p.p_name", + resultSetMapping = "person-id-and-name" + ), + @NamedNativeQuery( + name = "person-id-and-name2", + query = "select p.id, p.p_name, p.id as id2, p.p_name as p_name2 from person p order by p.p_name", + resultSetMapping = "person-id-and-name2" + ) + } ) public static class Person { @Id @@ -123,4 +157,31 @@ public class ConstructorResultNativeQueryTest extends BaseEntityManagerFunctiona em.getTransaction().commit(); em.close(); } + + + @Test + public void testMultipleConstructorResultNativeQuery() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.persist( new Person( 1, "John", new Date() ) ); + em.getTransaction().commit(); + em.close(); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + List results = em.createNamedQuery( "person-id-and-name2" ).getResultList(); + assertEquals( 1, results.size() ); + Object[] result = assertTyping( Object[].class, results.get( 0 ) ); + assertEquals( 2, result.length ); + assertTyping( Person.class, result[0] ); + assertTyping( Person.class, result[1] ); + em.getTransaction().commit(); + em.close(); + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + em.createQuery( "delete from Person" ).executeUpdate(); + em.getTransaction().commit(); + em.close(); + } }