diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/DomainQueryExecutionContext.java b/hibernate-core/src/main/java/org/hibernate/query/spi/DomainQueryExecutionContext.java index aaafafaff185..9b2bd5a0310e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/DomainQueryExecutionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/DomainQueryExecutionContext.java @@ -38,4 +38,8 @@ default boolean hasCallbackActions() { * The underlying session */ SharedSessionContractImplementor getSession(); + + default Class getResultType() { + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java index 3749c4f85010..a225e4895dc5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java @@ -136,11 +136,13 @@ public ConcreteSqmSelectQueryPlan( jdbcParameterBindings ); session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames(), true ); + //noinspection unchecked return session.getFactory().getJdbcServices().getJdbcSelectExecutor().list( jdbcSelect, jdbcParameterBindings, listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ), rowTransformer, + (Class) executionContext.getResultType(), uniqueSemantic ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardRowReader.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardRowReader.java index 50d8af97653b..9185b154e9a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardRowReader.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardRowReader.java @@ -6,6 +6,7 @@ */ package org.hibernate.sql.results.internal; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; @@ -32,6 +33,8 @@ public class StandardRowReader implements RowReader { private final Class domainResultJavaType; private final int assemblerCount; + private final ComponentType componentType; + private final Class resultElementClass; private static final Logger LOGGER = LoadingLogger.LOGGER; @@ -45,6 +48,16 @@ public StandardRowReader( this.rowTransformer = rowTransformer; this.assemblerCount = resultAssemblers.size(); this.domainResultJavaType = domainResultJavaType; + this.componentType = ComponentType.determineComponentType( domainResultJavaType ); + if ( domainResultJavaType == null + || domainResultJavaType == Object[].class + || domainResultJavaType == Object.class + || !domainResultJavaType.isArray() ) { + this.resultElementClass = Object.class; + } + else { + this.resultElementClass = domainResultJavaType.getComponentType(); + } } @Override @@ -86,20 +99,139 @@ public T readRow(RowProcessingState rowProcessingState, JdbcValuesSourceProcessi LOGGER.trace( "StandardRowReader#readRow" ); coordinateInitializers( rowProcessingState ); - final Object[] resultRow = new Object[ assemblerCount ]; final boolean debugEnabled = LOGGER.isDebugEnabled(); + // The following is ugly, but unfortunately necessary to not hurt performance. + // This implementation was micro-benchmarked and discussed with Francesco Nigro, + // who hinted that using this style instead of the reflective Array.getLength(), Array.set() + // is easier for the JVM to optimize + switch ( componentType ) { + case BOOLEAN: + final boolean[] resultBooleanRow = new boolean[assemblerCount]; - for ( int i = 0; i < assemblerCount; i++ ) { - final DomainResultAssembler assembler = resultAssemblers.get( i ); - if ( debugEnabled ) { - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); - } - resultRow[i] = assembler.assemble( rowProcessingState, options ); - } + for ( int i = 0; i < assemblerCount; i++ ) { + final DomainResultAssembler assembler = resultAssemblers.get( i ); + if ( debugEnabled ) { + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + } + resultBooleanRow[i] = (boolean) assembler.assemble( rowProcessingState, options ); + } + + afterRow( rowProcessingState ); + + return (T) resultBooleanRow; + case BYTE: + final byte[] resultByteRow = new byte[assemblerCount]; + + for ( int i = 0; i < assemblerCount; i++ ) { + final DomainResultAssembler assembler = resultAssemblers.get( i ); + if ( debugEnabled ) { + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + } + resultByteRow[i] = (byte) assembler.assemble( rowProcessingState, options ); + } + + afterRow( rowProcessingState ); + + return (T) resultByteRow; + case CHAR: + final char[] resultCharRow = new char[assemblerCount]; + + for ( int i = 0; i < assemblerCount; i++ ) { + final DomainResultAssembler assembler = resultAssemblers.get( i ); + if ( debugEnabled ) { + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + } + resultCharRow[i] = (char) assembler.assemble( rowProcessingState, options ); + } + + afterRow( rowProcessingState ); + + return (T) resultCharRow; + case SHORT: + final short[] resultShortRow = new short[assemblerCount]; + + for ( int i = 0; i < assemblerCount; i++ ) { + final DomainResultAssembler assembler = resultAssemblers.get( i ); + if ( debugEnabled ) { + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + } + resultShortRow[i] = (short) assembler.assemble( rowProcessingState, options ); + } + + afterRow( rowProcessingState ); + + return (T) resultShortRow; + case INT: + final int[] resultIntRow = new int[assemblerCount]; + + for ( int i = 0; i < assemblerCount; i++ ) { + final DomainResultAssembler assembler = resultAssemblers.get( i ); + if ( debugEnabled ) { + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + } + resultIntRow[i] = (int) assembler.assemble( rowProcessingState, options ); + } + + afterRow( rowProcessingState ); + + return (T) resultIntRow; + case LONG: + final long[] resultLongRow = new long[assemblerCount]; + + for ( int i = 0; i < assemblerCount; i++ ) { + final DomainResultAssembler assembler = resultAssemblers.get( i ); + if ( debugEnabled ) { + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + } + resultLongRow[i] = (long) assembler.assemble( rowProcessingState, options ); + } + + afterRow( rowProcessingState ); + + return (T) resultLongRow; + case FLOAT: + final float[] resultFloatRow = new float[assemblerCount]; + + for ( int i = 0; i < assemblerCount; i++ ) { + final DomainResultAssembler assembler = resultAssemblers.get( i ); + if ( debugEnabled ) { + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + } + resultFloatRow[i] = (float) assembler.assemble( rowProcessingState, options ); + } + + afterRow( rowProcessingState ); - afterRow( rowProcessingState ); + return (T) resultFloatRow; + case DOUBLE: + final double[] resultDoubleRow = new double[assemblerCount]; - return rowTransformer.transformRow( resultRow ); + for ( int i = 0; i < assemblerCount; i++ ) { + final DomainResultAssembler assembler = resultAssemblers.get( i ); + if ( debugEnabled ) { + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + } + resultDoubleRow[i] = (double) assembler.assemble( rowProcessingState, options ); + } + + afterRow( rowProcessingState ); + + return (T) resultDoubleRow; + default: + final Object[] resultRow = (Object[]) Array.newInstance( resultElementClass, assemblerCount ); + + for ( int i = 0; i < assemblerCount; i++ ) { + final DomainResultAssembler assembler = resultAssemblers.get( i ); + if ( debugEnabled ) { + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + } + resultRow[i] = assembler.assemble( rowProcessingState, options ); + } + + afterRow( rowProcessingState ); + + return rowTransformer.transformRow( resultRow ); + } } private void afterRow(RowProcessingState rowProcessingState) { @@ -118,4 +250,54 @@ public void finishUp(JdbcValuesSourceProcessingState processingState) { initializers.endLoading( processingState.getExecutionContext() ); } + enum ComponentType { + BOOLEAN(boolean.class), + BYTE(byte.class), + SHORT(short.class), + CHAR(char.class), + INT(int.class), + LONG(long.class), + FLOAT(float.class), + DOUBLE(double.class), + OBJECT(Object.class); + + private final Class componentType; + + ComponentType(Class componentType) { + this.componentType = componentType; + } + + public static ComponentType determineComponentType(Class resultType) { + if ( resultType == boolean[].class) { + return BOOLEAN; + } + else if ( resultType == byte[].class) { + return BYTE; + } + else if ( resultType == short[].class) { + return SHORT; + } + else if ( resultType == char[].class) { + return CHAR; + } + else if ( resultType == int[].class) { + return INT; + } + else if ( resultType == long[].class) { + return LONG; + } + else if ( resultType == float[].class) { + return FLOAT; + } + else if ( resultType == double[].class) { + return DOUBLE; + } + return OBJECT; + } + + public Class getComponentType() { + return componentType; + } + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/RowReader.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/RowReader.java index d545b4ef01f5..83c8b4bbc1b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/RowReader.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/RowReader.java @@ -37,7 +37,10 @@ public interface RowReader { * @apiNote along with {@link #getResultJavaTypes()}, describes the "raw" * values as determined from the {@link org.hibernate.sql.results.graph.DomainResult} * references associated with the JdbcValues being processed + * + * @deprecated Not used anymore */ + @Deprecated(forRemoval = true) Class getResultJavaType(); /** diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/MultiSelectResultTypeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/MultiSelectResultTypeTest.java index d204ef745dab..ba8467255c9b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/MultiSelectResultTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/MultiSelectResultTypeTest.java @@ -45,6 +45,7 @@ public void testResultOfMultiSelect(EntityManagerFactoryScope scope) { List idPairs = entityManager.createQuery( q ).getResultList(); assertThat( idPairs.size() ).isEqualTo( 1 ); Integer[] ids = idPairs.get( 0 ); + assertThat( ids[0] ).isEqualTo( 1 ); } ); } @@ -60,6 +61,7 @@ public void testResultOfMultiSelectPrimitive(EntityManagerFactoryScope scope) { List idPairs = entityManager.createQuery( q ).getResultList(); assertThat( idPairs.size() ).isEqualTo( 1 ); int[] ids = idPairs.get( 0 ); + assertThat( ids[0] ).isEqualTo( 1 ); } ); } @@ -75,8 +77,8 @@ public void testResultOfMultiSelect2(EntityManagerFactoryScope scope) { List values = entityManager.createQuery( q ).getResultList(); assertThat( values.size() ).isEqualTo( 1 ); Object[] value = values.get( 0 ); - Integer id = (Integer) value[0]; - String name = (String) value[1]; + assertThat( value[0] ).isEqualTo( 1 ); + assertThat( value[1] ).isEqualTo( "a" ); } ); } @@ -91,7 +93,7 @@ public void testResultOfSelect(EntityManagerFactoryScope scope) { q.select( r.get( "id" ) ); List idPairs = entityManager.createQuery( q ).getResultList(); assertThat( idPairs.size() ).isEqualTo( 1 ); - Integer id = idPairs.get( 0 ); + assertThat( idPairs.get( 0 ) ).isEqualTo( 1 ); } ); }