HHH-8498 - Full ConstructorResult handling

This commit is contained in:
Steve Ebersole 2013-09-17 17:28:58 -05:00
parent 4b2667cc40
commit a2881b3a35
12 changed files with 664 additions and 423 deletions

View File

@ -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

View File

@ -211,7 +211,10 @@ public class PrimitiveWrapperHelper {
return (PrimitiveWrapperDescriptor<X>) 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;
}
}

View File

@ -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
*/

View File

@ -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<Type> types, List<String> aliases) throws SQLException {
final List<Type> localTypes = new ArrayList<Type>();
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<Type> 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 );
}
}

View File

@ -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;
// }
}

View File

@ -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<Queryable> entityPersisters = new ArrayList<Queryable>();
List<Integer> entityOwners = new ArrayList<Integer>();
List<EntityAliases> entityAliases = new ArrayList<EntityAliases>();
List collectionPersisters = new ArrayList();
List collectionOwners = new ArrayList();
List collectionAliases = new ArrayList();
List<QueryableCollection> collectionPersisters = new ArrayList<QueryableCollection>();
List<Integer> collectionOwners = new ArrayList<Integer>();
List<CollectionAliases> collectionAliases = new ArrayList<CollectionAliases>();
List<LockMode> lockModes = new ArrayList<LockMode>();
List<ResultColumnProcessor> resultColumnProcessors = new ArrayList<ResultColumnProcessor>();
List<Return> nonScalarReturnList = new ArrayList<Return>();
List<Type> resultTypes = new ArrayList<Type>();
List<String> specifiedAliases = new ArrayList<String>();
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<Boolean> includeInResultRowList = new ArrayList<Boolean>();
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.
* <p/>
* 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<Type> types, List<String> 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<Type> types, List<String> 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<Type> types, List<String> 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<Type> types, List<String> aliases) throws SQLException {
final List<Type> localTypes = new ArrayList<Type>();
for ( ScalarResultColumnProcessor scalar : scalarProcessors ) {
scalar.performDiscovery( metadata, localTypes, aliases );
}
types.addAll( localTypes );
constructor = resolveConstructor( targetClass, localTypes );
}
}
private static Constructor resolveConstructor(Class targetClass, List<Type> 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<String> aliases = new ArrayList<String>();
List<Type> types = new ArrayList<Type>();
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
)
);
}
}
}

View File

@ -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
)
);
}
}

View File

@ -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<Type> types, List<String> aliases) {
// nothing to discover for non-scalar results
}
@Override
public Object extract(Object[] data, ResultSet resultSet, SessionImplementor session)
throws SQLException, HibernateException {
return data[ position ];
}
}

View File

@ -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<Type> types, List<String> 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;
}

View File

@ -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.
* <p/>
* 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;
}
}

View File

@ -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<Type> types, List<String> 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 );
}
}

View File

@ -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();
}
}