HHH-18271 Improve fast path for row reading

This commit is contained in:
Christian Beikov 2024-08-02 12:42:40 +02:00 committed by Steve Ebersole
parent e9513b1db5
commit cf44c30bf2
6 changed files with 98 additions and 130 deletions

View File

@ -40,7 +40,7 @@ import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.exec.spi.JdbcParametersList;
import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.internal.ImmutableFetchList; import org.hibernate.sql.results.graph.internal.ImmutableFetchList;
import org.hibernate.sql.results.internal.RowTransformerDatabaseSnapshotImpl; import org.hibernate.sql.results.internal.RowTransformerArrayImpl;
import org.hibernate.sql.results.spi.ListResultsConsumer; import org.hibernate.sql.results.spi.ListResultsConsumer;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.StandardBasicTypes;
@ -176,7 +176,7 @@ class DatabaseSnapshotExecutor {
jdbcSelect, jdbcSelect,
jdbcParameterBindings, jdbcParameterBindings,
new BaseExecutionContext( session ), new BaseExecutionContext( session ),
RowTransformerDatabaseSnapshotImpl.instance(), RowTransformerArrayImpl.instance(),
null, null,
ListResultsConsumer.UniqueSemantic.FILTER, ListResultsConsumer.UniqueSemantic.FILTER,
1 1

View File

@ -6,16 +6,13 @@
*/ */
package org.hibernate.loader.ast.internal; package org.hibernate.loader.ast.internal;
import java.util.List;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.exec.spi.JdbcParametersList;
import org.hibernate.sql.results.internal.RowTransformerDatabaseSnapshotImpl; import org.hibernate.sql.results.internal.RowTransformerArrayImpl;
import org.hibernate.sql.results.spi.RowTransformer; import org.hibernate.sql.results.spi.RowTransformer;
/** /**
@ -37,7 +34,7 @@ public class SingleIdArrayLoadPlan extends SingleIdLoadPlan<Object[]> {
@Override @Override
protected RowTransformer<Object[]> getRowTransformer() { protected RowTransformer<Object[]> getRowTransformer() {
return RowTransformerDatabaseSnapshotImpl.instance(); return RowTransformerArrayImpl.instance();
} }
} }

View File

@ -82,7 +82,7 @@ public class LoadingCollectionEntryImpl implements LoadingCollectionEntry {
final boolean hasNoQueuedAdds = collectionInstance.endRead(); final boolean hasNoQueuedAdds = collectionInstance.endRead();
final SharedSessionContractImplementor session = executionContext.getSession(); final SharedSessionContractImplementor session = executionContext.getSession();
final PersistenceContext persistenceContext = session.getPersistenceContext(); final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final CollectionPersister collectionDescriptor = getCollectionDescriptor(); final CollectionPersister collectionDescriptor = getCollectionDescriptor();
ResultsHelper.finalizeCollectionLoading( ResultsHelper.finalizeCollectionLoading(

View File

@ -1,33 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.results.internal;
import org.hibernate.sql.results.spi.RowTransformer;
/**
* @author Andrea Boriero
*/
public class RowTransformerDatabaseSnapshotImpl<T> implements RowTransformer<T> {
/**
* Singleton access
*/
public static final RowTransformerDatabaseSnapshotImpl INSTANCE = new RowTransformerDatabaseSnapshotImpl();
@SuppressWarnings("unchecked")
public static <T> RowTransformerDatabaseSnapshotImpl<T> instance() {
return INSTANCE;
}
private RowTransformerDatabaseSnapshotImpl() {
}
@Override
@SuppressWarnings("unchecked")
public T transformRow(Object[] row) {
return (T) row;
}
}

View File

@ -39,7 +39,7 @@ public class StandardRowReader<T> implements RowReader<T> {
private final Initializer<InitializerData>[] sortedForResolveInstance; private final Initializer<InitializerData>[] sortedForResolveInstance;
private final InitializerData[] sortedForResolveInstanceData; private final InitializerData[] sortedForResolveInstanceData;
private final boolean hasCollectionInitializers; private final boolean hasCollectionInitializers;
private final RowTransformer<T> rowTransformer; private final @Nullable RowTransformer<T> rowTransformer;
private final Class<T> domainResultJavaType; private final Class<T> domainResultJavaType;
private final ComponentType componentType; private final ComponentType componentType;
@ -78,7 +78,11 @@ public class StandardRowReader<T> implements RowReader<T> {
this.sortedForResolveInstance = (Initializer<InitializerData>[]) sortedForResolveInitializers; this.sortedForResolveInstance = (Initializer<InitializerData>[]) sortedForResolveInitializers;
this.sortedForResolveInstanceData = new InitializerData[sortedForResolveInstance.length]; this.sortedForResolveInstanceData = new InitializerData[sortedForResolveInstance.length];
this.hasCollectionInitializers = hasCollectionInitializers; this.hasCollectionInitializers = hasCollectionInitializers;
this.rowTransformer = rowTransformer; this.rowTransformer = rowTransformer == RowTransformerArrayImpl.INSTANCE && resultAssemblers.length != 1
|| rowTransformer == RowTransformerStandardImpl.INSTANCE
|| rowTransformer == RowTransformerSingularReturnImpl.INSTANCE && resultAssemblers.length == 1
? null
: rowTransformer;
this.domainResultJavaType = domainResultJavaType; this.domainResultJavaType = domainResultJavaType;
if ( domainResultJavaType == null if ( domainResultJavaType == null
|| domainResultJavaType == Object[].class || domainResultJavaType == Object[].class
@ -136,6 +140,32 @@ public class StandardRowReader<T> implements RowReader<T> {
public T readRow(RowProcessingState rowProcessingState) { public T readRow(RowProcessingState rowProcessingState) {
coordinateInitializers( rowProcessingState ); coordinateInitializers( rowProcessingState );
final T result;
if ( componentType != ComponentType.OBJECT ) {
result = readPrimitiveRow( rowProcessingState );
}
else {
if ( resultAssemblers.length == 1 && rowTransformer == null ) {
//noinspection unchecked
result = (T) resultAssemblers[0].assemble( rowProcessingState );
}
else {
final Object[] resultRow = (Object[]) Array.newInstance( resultElementClass, resultAssemblers.length );
for ( int i = 0; i < resultAssemblers.length; i++ ) {
resultRow[i] = resultAssemblers[i].assemble( rowProcessingState );
}
//noinspection unchecked
result = rowTransformer == null
? (T) resultRow
: rowTransformer.transformRow( resultRow );
}
}
finishUpRow();
return result;
}
private T readPrimitiveRow(RowProcessingState rowProcessingState) {
// The following is ugly, but unfortunately necessary to not hurt performance. // The following is ugly, but unfortunately necessary to not hurt performance.
// This implementation was micro-benchmarked and discussed with Francesco Nigro, // This implementation was micro-benchmarked and discussed with Francesco Nigro,
// who hinted that using this style instead of the reflective Array.getLength(), Array.set() // who hinted that using this style instead of the reflective Array.getLength(), Array.set()
@ -143,110 +173,57 @@ public class StandardRowReader<T> implements RowReader<T> {
switch ( componentType ) { switch ( componentType ) {
case BOOLEAN: case BOOLEAN:
final boolean[] resultBooleanRow = new boolean[resultAssemblers.length]; final boolean[] resultBooleanRow = new boolean[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) { for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i]; resultBooleanRow[i] = (boolean) resultAssemblers[i].assemble( rowProcessingState );
resultBooleanRow[i] = (boolean) assembler.assemble( rowProcessingState );
} }
afterRow( rowProcessingState );
return (T) resultBooleanRow; return (T) resultBooleanRow;
case BYTE: case BYTE:
final byte[] resultByteRow = new byte[resultAssemblers.length]; final byte[] resultByteRow = new byte[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) { for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i]; resultByteRow[i] = (byte) resultAssemblers[i].assemble( rowProcessingState );
resultByteRow[i] = (byte) assembler.assemble( rowProcessingState );
} }
afterRow( rowProcessingState );
return (T) resultByteRow; return (T) resultByteRow;
case CHAR: case CHAR:
final char[] resultCharRow = new char[resultAssemblers.length]; final char[] resultCharRow = new char[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) { for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i]; resultCharRow[i] = (char) resultAssemblers[i].assemble( rowProcessingState );
resultCharRow[i] = (char) assembler.assemble( rowProcessingState );
} }
afterRow( rowProcessingState );
return (T) resultCharRow; return (T) resultCharRow;
case SHORT: case SHORT:
final short[] resultShortRow = new short[resultAssemblers.length]; final short[] resultShortRow = new short[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) { for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i]; resultShortRow[i] = (short) resultAssemblers[i].assemble( rowProcessingState );
resultShortRow[i] = (short) assembler.assemble( rowProcessingState );
} }
afterRow( rowProcessingState );
return (T) resultShortRow; return (T) resultShortRow;
case INT: case INT:
final int[] resultIntRow = new int[resultAssemblers.length]; final int[] resultIntRow = new int[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) { for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i]; resultIntRow[i] = (int) resultAssemblers[i].assemble( rowProcessingState );
resultIntRow[i] = (int) assembler.assemble( rowProcessingState );
} }
afterRow( rowProcessingState );
return (T) resultIntRow; return (T) resultIntRow;
case LONG: case LONG:
final long[] resultLongRow = new long[resultAssemblers.length]; final long[] resultLongRow = new long[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) { for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i]; resultLongRow[i] = (long) resultAssemblers[i].assemble( rowProcessingState );
resultLongRow[i] = (long) assembler.assemble( rowProcessingState );
} }
afterRow( rowProcessingState );
return (T) resultLongRow; return (T) resultLongRow;
case FLOAT: case FLOAT:
final float[] resultFloatRow = new float[resultAssemblers.length]; final float[] resultFloatRow = new float[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) { for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i]; resultFloatRow[i] = (float) resultAssemblers[i].assemble( rowProcessingState );
resultFloatRow[i] = (float) assembler.assemble( rowProcessingState );
} }
afterRow( rowProcessingState );
return (T) resultFloatRow; return (T) resultFloatRow;
case DOUBLE: case DOUBLE:
final double[] resultDoubleRow = new double[resultAssemblers.length]; final double[] resultDoubleRow = new double[resultAssemblers.length];
for ( int i = 0; i < resultAssemblers.length; i++ ) { for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i]; resultDoubleRow[i] = (double) resultAssemblers[i].assemble( rowProcessingState );
resultDoubleRow[i] = (double) assembler.assemble( rowProcessingState );
} }
afterRow( rowProcessingState );
return (T) resultDoubleRow; return (T) resultDoubleRow;
default: default:
final Object[] resultRow = (Object[]) Array.newInstance( resultElementClass, resultAssemblers.length ); throw new AssertionError( "Object should be handled specially" );
for ( int i = 0; i < resultAssemblers.length; i++ ) {
final DomainResultAssembler assembler = resultAssemblers[i];
resultRow[i] = assembler.assemble( rowProcessingState );
}
afterRow( rowProcessingState );
return rowTransformer.transformRow( resultRow );
} }
} }
private void afterRow(RowProcessingState rowProcessingState) {
finishUpRow();
}
private void finishUpRow() { private void finishUpRow() {
for ( InitializerData data : initializersData ) { for ( InitializerData data : initializersData ) {
data.setState( Initializer.State.UNINITIALIZED ); data.setState( Initializer.State.UNINITIALIZED );

View File

@ -105,7 +105,7 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
} }
private static class Results<R> { private static class Results<R> {
private final List<R> results; private final ArrayList<R> results;
private final JavaType<R> resultJavaType; private final JavaType<R> resultJavaType;
public Results(JavaType<R> resultJavaType, int initialSize) { public Results(JavaType<R> resultJavaType, int initialSize) {
@ -188,37 +188,17 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
results = new Results<>( domainResultJavaType, initialCollectionSize ); results = new Results<>( domainResultJavaType, initialCollectionSize );
} }
int readRows = 0; final int readRows;
if ( uniqueSemantic == UniqueSemantic.FILTER if ( uniqueSemantic == UniqueSemantic.FILTER
|| uniqueSemantic == UniqueSemantic.ASSERT && rowReader.hasCollectionInitializers() || uniqueSemantic == UniqueSemantic.ASSERT && rowReader.hasCollectionInitializers()
|| uniqueSemantic == UniqueSemantic.ALLOW && isEntityResultType ) { || uniqueSemantic == UniqueSemantic.ALLOW && isEntityResultType ) {
while ( rowProcessingState.next() ) { readRows = readUnique( rowProcessingState, rowReader, results );
final boolean added = results.addUnique( rowReader.readRow( rowProcessingState ) );
rowProcessingState.finishRowProcessing( added );
readRows++;
}
} }
else if ( uniqueSemantic == UniqueSemantic.ASSERT ) { else if ( uniqueSemantic == UniqueSemantic.ASSERT ) {
while ( rowProcessingState.next() ) { readRows = readUniqueAssert( rowProcessingState, rowReader, results );
if ( !results.addUnique( rowReader.readRow( rowProcessingState ) ) ) {
throw new HibernateException(
String.format(
Locale.ROOT,
"Duplicate row was found and `%s` was specified",
UniqueSemantic.ASSERT
)
);
}
rowProcessingState.finishRowProcessing( true );
readRows++;
}
} }
else { else {
while ( rowProcessingState.next() ) { readRows = read( rowProcessingState, rowReader, results );
results.add( rowReader.readRow( rowProcessingState ) );
rowProcessingState.finishRowProcessing( true );
readRows++;
}
} }
rowReader.finishUp( rowProcessingState ); rowReader.finishUp( rowProcessingState );
@ -260,6 +240,53 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
throw new IllegalStateException( "Should not reach this" ); throw new IllegalStateException( "Should not reach this" );
} }
private static <R> int read(
RowProcessingStateStandardImpl rowProcessingState,
RowReader<R> rowReader,
Results<R> results) {
int readRows = 0;
while ( rowProcessingState.next() ) {
results.add( rowReader.readRow( rowProcessingState ) );
rowProcessingState.finishRowProcessing( true );
readRows++;
}
return readRows;
}
private static <R> int readUniqueAssert(
RowProcessingStateStandardImpl rowProcessingState,
RowReader<R> rowReader,
Results<R> results) {
int readRows = 0;
while ( rowProcessingState.next() ) {
if ( !results.addUnique( rowReader.readRow( rowProcessingState ) ) ) {
throw new HibernateException(
String.format(
Locale.ROOT,
"Duplicate row was found and `%s` was specified",
UniqueSemantic.ASSERT
)
);
}
rowProcessingState.finishRowProcessing( true );
readRows++;
}
return readRows;
}
private static <R> int readUnique(
RowProcessingStateStandardImpl rowProcessingState,
RowReader<R> rowReader,
Results<R> results) {
int readRows = 0;
while ( rowProcessingState.next() ) {
final boolean added = results.addUnique( rowReader.readRow( rowProcessingState ) );
rowProcessingState.finishRowProcessing( added );
readRows++;
}
return readRows;
}
private JavaType<R> resolveDomainResultJavaType( private JavaType<R> resolveDomainResultJavaType(
Class<R> domainResultResultJavaType, Class<R> domainResultResultJavaType,
List<@Nullable JavaType<?>> resultJavaTypes, List<@Nullable JavaType<?>> resultJavaTypes,