* Verified named "result mapping" support - e.g. JPA's `@SqlResultMapping`; gets stored correctly, but can't test it getting applied correctly until after native-query support is implemented
* Verified / implemented query-result cache support
This commit is contained in:
parent
ac4af03ea3
commit
1f1f5f118b
|
@ -98,40 +98,51 @@ public class QueryKey implements Serializable {
|
|||
private int generateHashCode() {
|
||||
int result = 13;
|
||||
result = 37 * result + sqlQueryString.hashCode();
|
||||
result = 37 * result + ( firstRow==null ? 0 : firstRow.hashCode() );
|
||||
result = 37 * result + ( maxRows==null ? 0 : maxRows.hashCode() );
|
||||
result = 37 * result + parameterBindingsMemento.hashCode();
|
||||
result = 37 * result + ( enabledFilterNames == null ? 0 : enabledFilterNames.hashCode() );
|
||||
result = 37 * result + ( firstRow==null ? 0 : firstRow );
|
||||
result = 37 * result + ( maxRows==null ? 0 : maxRows );
|
||||
result = 37 * result + ( tenantIdentifier==null ? 0 : tenantIdentifier.hashCode() );
|
||||
// the collections are too complicated to incorporate into the hashcode. but they really
|
||||
// aren't needed in the hashcode calculation - they are handled in `#equals` and the calculation
|
||||
// without them is a good hashing code.
|
||||
//
|
||||
// todo (6.0) : maybe even just base it on `sqlQueryString`?
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
@SuppressWarnings({"RedundantIfStatement", "EqualsWhichDoesntCheckParameterClass"})
|
||||
public boolean equals(Object other) {
|
||||
if ( !( other instanceof QueryKey ) ) {
|
||||
// it should never be another type, so skip the instanceof check and just do the cast
|
||||
final QueryKey that;
|
||||
|
||||
try {
|
||||
that = (QueryKey) other;
|
||||
}
|
||||
catch (ClassCastException cce) {
|
||||
// treat this as the exception case
|
||||
return false;
|
||||
}
|
||||
|
||||
final QueryKey that = (QueryKey) other;
|
||||
if ( !sqlQueryString.equals( that.sqlQueryString ) ) {
|
||||
if ( ! Objects.equals( sqlQueryString, that.sqlQueryString ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !Objects.equals( tenantIdentifier, that.tenantIdentifier ) ) {
|
||||
if ( ! Objects.equals( tenantIdentifier, that.tenantIdentifier ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !Objects.equals( firstRow, that.firstRow )
|
||||
|| !Objects.equals( maxRows, that.maxRows ) ) {
|
||||
if ( ! Objects.equals( firstRow, that.firstRow )
|
||||
|| ! Objects.equals( maxRows, that.maxRows ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !Objects.equals( parameterBindingsMemento, that.parameterBindingsMemento ) ) {
|
||||
// Set's `#equals` impl does a deep check, so `Objects#equals` is a good check
|
||||
if ( ! Objects.equals( parameterBindingsMemento, that.parameterBindingsMemento ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !Objects.equals( enabledFilterNames, that.enabledFilterNames ) ) {
|
||||
if ( ! Objects.equals( enabledFilterNames, that.enabledFilterNames ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,10 +24,9 @@ import static org.jboss.logging.Logger.Level.WARN;
|
|||
@MessageLogger( projectCode = "HHH" )
|
||||
@ValidIdRange( min = 90001001, max = 90002000 )
|
||||
public interface SecondLevelCacheLogger extends BasicLogger {
|
||||
SecondLevelCacheLogger INSTANCE = Logger.getMessageLogger(
|
||||
SecondLevelCacheLogger.class,
|
||||
"org.hibernate.orm.cache"
|
||||
);
|
||||
String LOGGER_NAME = "org.hibernate.orm.cache";
|
||||
|
||||
SecondLevelCacheLogger INSTANCE = Logger.getMessageLogger( SecondLevelCacheLogger.class, LOGGER_NAME );
|
||||
|
||||
boolean DEBUG_ENABLED = INSTANCE.isDebugEnabled();
|
||||
boolean TRACE_ENABLED = INSTANCE.isTraceEnabled();
|
||||
|
|
|
@ -91,7 +91,7 @@ public abstract class AbstractScrollableResults<R> implements ScrollableResultsI
|
|||
return;
|
||||
}
|
||||
|
||||
getJdbcValues().finishUp();
|
||||
getJdbcValues().finishUp( persistenceContext );
|
||||
getPersistenceContext().getJdbcCoordinator().afterStatementExecution();
|
||||
|
||||
this.closed = true;
|
||||
|
|
|
@ -12,8 +12,10 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.NotYetImplementedFor6Exception;
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.QueryParameterException;
|
||||
import org.hibernate.cache.spi.QueryKey;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.query.QueryParameter;
|
||||
|
@ -174,4 +176,24 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryKey.ParameterBindingsMemento generateQueryKeyMemento() {
|
||||
// todo (6.0) : need to decide how to handle
|
||||
if ( parameterMetadata.getParameterCount() == 0 && CollectionHelper.isEmpty( parameterBindingMap ) ) {
|
||||
return new QueryKey.ParameterBindingsMemento() {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return QueryParameterBindingsImpl.class.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof QueryKey.ParameterBindingsMemento;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
throw new NotYetImplementedFor6Exception( getClass() );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ package org.hibernate.query.named;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface RowReaderMemento {
|
||||
Class[] getResultClasses();
|
||||
Class<?>[] getResultClasses();
|
||||
|
||||
String[] getResultMappingNames();
|
||||
}
|
||||
|
|
|
@ -23,17 +23,18 @@ import org.hibernate.engine.spi.PersistenceContext;
|
|||
import org.hibernate.loader.ast.spi.AfterLoadAction;
|
||||
import org.hibernate.query.internal.ScrollableResultsIterator;
|
||||
import org.hibernate.query.spi.ScrollableResultsImplementor;
|
||||
import org.hibernate.sql.exec.SqlExecLogger;
|
||||
import org.hibernate.sql.exec.spi.ExecutionContext;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||
import org.hibernate.sql.exec.spi.JdbcSelect;
|
||||
import org.hibernate.sql.exec.spi.JdbcSelectExecutor;
|
||||
import org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess;
|
||||
import org.hibernate.sql.results.internal.Helper;
|
||||
import org.hibernate.sql.results.internal.RowProcessingStateStandardImpl;
|
||||
import org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess;
|
||||
import org.hibernate.sql.results.jdbc.internal.JdbcValuesCacheHit;
|
||||
import org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl;
|
||||
import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl;
|
||||
import org.hibernate.sql.results.jdbc.internal.ResultSetAccess;
|
||||
import org.hibernate.sql.results.internal.RowProcessingStateStandardImpl;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValues;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
|
||||
|
@ -43,8 +44,6 @@ import org.hibernate.sql.results.spi.RowReader;
|
|||
import org.hibernate.sql.results.spi.RowTransformer;
|
||||
import org.hibernate.sql.results.spi.ScrollableResultsConsumer;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -61,8 +60,6 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
*/
|
||||
public static final JdbcSelectExecutorStandardImpl INSTANCE = new JdbcSelectExecutorStandardImpl();
|
||||
|
||||
private static final Logger log = Logger.getLogger( JdbcSelectExecutorStandardImpl.class );
|
||||
|
||||
@Override
|
||||
public <R> List<R> list(
|
||||
JdbcSelect jdbcSelect,
|
||||
|
@ -254,7 +251,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
final QueryKey queryResultsCacheKey;
|
||||
|
||||
if ( queryCacheEnabled && cacheMode.isGetEnabled() ) {
|
||||
log.debugf( "Reading Query result cache data per CacheMode#isGetEnabled [%s]", cacheMode.name() );
|
||||
SqlExecLogger.INSTANCE.debugf( "Reading Query result cache data per CacheMode#isGetEnabled [%s]", cacheMode.name() );
|
||||
|
||||
final QueryResultsCache queryCache = executionContext.getSession().getFactory()
|
||||
.getCache()
|
||||
|
@ -290,7 +287,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
// invalidation strategies - QueryCacheInvalidationStrategy
|
||||
}
|
||||
else {
|
||||
log.debugf( "Skipping reading Query result cache data: cache-enabled = %s, cache-mode = %s",
|
||||
SqlExecLogger.INSTANCE.debugf( "Skipping reading Query result cache data: cache-enabled = %s, cache-mode = %s",
|
||||
queryCacheEnabled,
|
||||
cacheMode.name()
|
||||
);
|
||||
|
|
|
@ -6,11 +6,13 @@
|
|||
*/
|
||||
package org.hibernate.sql.results.caching;
|
||||
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface QueryCachePutManager {
|
||||
void registerJdbcRow(Object[] values);
|
||||
|
||||
void finishUp();
|
||||
void finishUp(SharedSessionContractImplementor session);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.sql.results.caching.internal;
|
||||
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.sql.results.caching.QueryCachePutManager;
|
||||
|
||||
/**
|
||||
|
@ -28,7 +29,7 @@ public class QueryCachePutManagerDisabledImpl implements QueryCachePutManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void finishUp() {
|
||||
public void finishUp(SharedSessionContractImplementor session) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
package org.hibernate.sql.results.caching.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.cache.spi.QueryKey;
|
||||
import org.hibernate.cache.spi.QueryResultsCache;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.sql.results.caching.QueryCachePutManager;
|
||||
|
||||
/**
|
||||
|
@ -35,16 +37,21 @@ public class QueryCachePutManagerEnabledImpl implements QueryCachePutManager {
|
|||
if ( dataToCache == null ) {
|
||||
dataToCache = new ArrayList<>();
|
||||
}
|
||||
dataToCache.add( values );
|
||||
|
||||
// todo (6.0) : verify whether we really need to copy these..
|
||||
// `RowProcessingStateStandardImpl` (see `#finishRowProcessing`) already creates new array
|
||||
// instances for each row
|
||||
// dataToCache.add( values );
|
||||
dataToCache.add( Arrays.copyOf( values, values.length ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishUp() {
|
||||
public void finishUp(SharedSessionContractImplementor session) {
|
||||
// todo (
|
||||
queryCache.put(
|
||||
queryKey,
|
||||
dataToCache,
|
||||
// todo (6.0) : needs access to Session to pass along to cache call
|
||||
null
|
||||
session
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,23 +49,15 @@ public class Helper {
|
|||
}
|
||||
|
||||
private static Consumer<Initializer> getInitializerConsumer(List<Initializer> initializers) {
|
||||
if ( ResultsLogger.INSTANCE.isDebugEnabled() ) {
|
||||
return initializer -> {
|
||||
ResultsLogger.INSTANCE.debug( "Adding initializer : " + initializer );
|
||||
addIfNotPresent( initializers, initializer );
|
||||
};
|
||||
}
|
||||
else {
|
||||
return initializer ->
|
||||
addIfNotPresent( initializers, initializer );
|
||||
}
|
||||
}
|
||||
return initializer -> {
|
||||
ResultsLogger.INSTANCE.debugf( "Initializer registration : %s", initializer );
|
||||
if ( initializers.contains( initializer ) ) {
|
||||
ResultsLogger.INSTANCE.debug( "Skipping initializer registration - already registered" );
|
||||
}
|
||||
|
||||
private static void addIfNotPresent(List<Initializer> initializers, Initializer initializer) {
|
||||
if ( initializers.contains( initializer ) ) {
|
||||
return;
|
||||
}
|
||||
initializers.add( initializer );
|
||||
ResultsLogger.INSTANCE.debugf( "Adding initializer : %s", initializer );
|
||||
initializers.add( initializer );
|
||||
};
|
||||
}
|
||||
|
||||
public static void finalizeCollectionLoading(
|
||||
|
|
|
@ -36,6 +36,7 @@ public class RowProcessingStateStandardImpl implements RowProcessingState {
|
|||
|
||||
private final Initializer[] initializers;
|
||||
|
||||
private final RowReader<?> rowReader;
|
||||
private final JdbcValues jdbcValues;
|
||||
private Object[] currentRowJdbcValues;
|
||||
|
||||
|
@ -46,6 +47,7 @@ public class RowProcessingStateStandardImpl implements RowProcessingState {
|
|||
JdbcValues jdbcValues) {
|
||||
this.resultSetProcessingState = resultSetProcessingState;
|
||||
this.queryOptions = queryOptions;
|
||||
this.rowReader = rowReader;
|
||||
this.jdbcValues = jdbcValues;
|
||||
|
||||
final List<Initializer> initializers = rowReader.getInitializers();
|
||||
|
@ -63,6 +65,11 @@ public class RowProcessingStateStandardImpl implements RowProcessingState {
|
|||
return resultSetProcessingState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RowReader<?> getRowReader() {
|
||||
return rowReader;
|
||||
}
|
||||
|
||||
public boolean next() throws SQLException {
|
||||
if ( jdbcValues.next( this ) ) {
|
||||
currentRowJdbcValues = jdbcValues.getCurrentRowValuesArray();
|
||||
|
|
|
@ -11,9 +11,9 @@ import java.util.List;
|
|||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.query.named.RowReaderMemento;
|
||||
import org.hibernate.sql.exec.spi.Callback;
|
||||
import org.hibernate.sql.results.graph.collection.CollectionInitializer;
|
||||
import org.hibernate.sql.results.graph.DomainResultAssembler;
|
||||
import org.hibernate.sql.results.graph.Initializer;
|
||||
import org.hibernate.sql.results.graph.collection.CollectionInitializer;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState;
|
||||
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
|
||||
|
@ -25,6 +25,7 @@ import org.jboss.logging.Logger;
|
|||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class StandardRowReader<T> implements RowReader<T> {
|
||||
private static final Logger LOG = Logger.getLogger( StandardRowReader.class );
|
||||
|
||||
|
@ -35,7 +36,6 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
private final int assemblerCount;
|
||||
private final Callback callback;
|
||||
|
||||
private final Object[] resultRow;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public StandardRowReader(
|
||||
|
@ -49,15 +49,13 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
|
||||
this.assemblerCount = resultAssemblers.size();
|
||||
this.callback = callback;
|
||||
|
||||
this.resultRow = new Object[assemblerCount];
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<T> getResultJavaType() {
|
||||
if ( resultAssemblers.size() == 1 ) {
|
||||
return resultAssemblers.get( 0 ).getAssembledJavaTypeDescriptor().getJavaType();
|
||||
return (Class<T>) resultAssemblers.get( 0 ).getAssembledJavaTypeDescriptor().getJavaType();
|
||||
}
|
||||
|
||||
return (Class<T>) Object[].class;
|
||||
|
@ -74,6 +72,8 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
|
||||
coordinateInitializers( rowProcessingState, options );
|
||||
|
||||
final Object[] resultRow = new Object[ assemblerCount ];
|
||||
|
||||
for ( int i = 0; i < assemblerCount; i++ ) {
|
||||
resultRow[i] = resultAssemblers.get( i ).assemble( rowProcessingState, options );
|
||||
}
|
||||
|
@ -91,6 +91,7 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
private void coordinateInitializers(
|
||||
RowProcessingState rowProcessingState,
|
||||
JdbcValuesSourceProcessingOptions options) {
|
||||
|
@ -143,6 +144,7 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
public void finishUp(JdbcValuesSourceProcessingState processingState) {
|
||||
for ( int i = 0; i < initializers.size(); i++ ) {
|
||||
initializers.get( i ).endLoading( processingState.getExecutionContext() );
|
||||
|
@ -156,7 +158,7 @@ public class StandardRowReader<T> implements RowReader<T> {
|
|||
public RowReaderMemento toMemento(SessionFactoryImplementor factory) {
|
||||
return new RowReaderMemento() {
|
||||
@Override
|
||||
public Class[] getResultClasses() {
|
||||
public Class<?>[] getResultClasses() {
|
||||
return new Class[0];
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.sql.results.jdbc.internal;
|
|||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.sql.results.caching.QueryCachePutManager;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValues;
|
||||
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
|
||||
|
@ -27,17 +28,19 @@ public abstract class AbstractJdbcValues implements JdbcValues {
|
|||
|
||||
@Override
|
||||
public final boolean next(RowProcessingState rowProcessingState) throws SQLException {
|
||||
if ( getCurrentRowValuesArray() != null ) {
|
||||
final boolean hadRow = processNext( rowProcessingState );
|
||||
if ( hadRow ) {
|
||||
|
||||
queryCachePutManager.registerJdbcRow( getCurrentRowValuesArray() );
|
||||
}
|
||||
return processNext( rowProcessingState );
|
||||
return hadRow;
|
||||
}
|
||||
|
||||
protected abstract boolean processNext(RowProcessingState rowProcessingState);
|
||||
|
||||
@Override
|
||||
public final void finishUp() {
|
||||
queryCachePutManager.finishUp();
|
||||
public final void finishUp(SharedSessionContractImplementor session) {
|
||||
queryCachePutManager.finishUp( session );
|
||||
release();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ package org.hibernate.sql.results.jdbc.internal;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.sql.results.ResultsLogger;
|
||||
import org.hibernate.sql.results.caching.internal.QueryCachePutManagerDisabledImpl;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping;
|
||||
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
|
||||
|
@ -18,14 +20,19 @@ import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public class JdbcValuesCacheHit extends AbstractJdbcValues {
|
||||
private static final Object[][] NO_DATA = new Object[0][];
|
||||
|
||||
private Object[][] cachedData;
|
||||
private final int numberOfRows;
|
||||
private JdbcValuesMapping resolvedMapping;
|
||||
private int position = -1;
|
||||
|
||||
public JdbcValuesCacheHit(Object[][] cachedData, JdbcValuesMapping resolvedMapping) {
|
||||
// if we have a cache hit we should not be writting back to the cache.
|
||||
// if we have a cache hit we should not be writing back to the cache.
|
||||
// its silly because the state would always be the same.
|
||||
//
|
||||
// well actually, there are times when we want to write values back to the cache even though we had a hit...
|
||||
// the case is related to the domain-data cache
|
||||
super( QueryCachePutManagerDisabledImpl.INSTANCE );
|
||||
this.cachedData = cachedData;
|
||||
this.numberOfRows = cachedData.length;
|
||||
|
@ -33,25 +40,38 @@ public class JdbcValuesCacheHit extends AbstractJdbcValues {
|
|||
}
|
||||
|
||||
public JdbcValuesCacheHit(List<Object[]> cachedResults, JdbcValuesMapping resolvedMapping) {
|
||||
this( (Object[][]) cachedResults.toArray(), resolvedMapping );
|
||||
this( extractData( cachedResults ), resolvedMapping );
|
||||
}
|
||||
|
||||
private static Object[][] extractData(List<Object[]> cachedResults) {
|
||||
if ( CollectionHelper.isEmpty( cachedResults ) ) {
|
||||
return NO_DATA;
|
||||
}
|
||||
|
||||
final Object[][] data = new Object[cachedResults.size()][];
|
||||
for ( int i = 0; i < cachedResults.size(); i++ ) {
|
||||
final Object[] row = cachedResults.get( i );
|
||||
data[ i ] = row;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processNext(RowProcessingState rowProcessingState) {
|
||||
// NOTE : explicitly skipping limit handling under the truth that
|
||||
// because the cached state ought to be the same size since
|
||||
// the cache key includes limits
|
||||
if ( isExhausted() ) {
|
||||
ResultsLogger.INSTANCE.tracef( "JdbcValuesCacheHit#processNext : position = %i; numberOfRows = %i", position, numberOfRows );
|
||||
|
||||
// NOTE : explicitly skipping limit handling because the cached state ought
|
||||
// already be the limited size since the cache key includes limits
|
||||
|
||||
if ( position >= numberOfRows - 1 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
position++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isExhausted() {
|
||||
return position >= numberOfRows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcValuesMapping getValuesMapping() {
|
||||
return resolvedMapping;
|
||||
|
@ -59,7 +79,7 @@ public class JdbcValuesCacheHit extends AbstractJdbcValues {
|
|||
|
||||
@Override
|
||||
public Object[] getCurrentRowValuesArray() {
|
||||
if ( isExhausted() ) {
|
||||
if ( position >= numberOfRows ) {
|
||||
return null;
|
||||
}
|
||||
return cachedData[position];
|
||||
|
|
|
@ -8,6 +8,8 @@ package org.hibernate.sql.results.jdbc.spi;
|
|||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
|
||||
/**
|
||||
* Provides unified access to query results (JDBC values - see
|
||||
* {@link RowProcessingState#getJdbcValue} whether they come from
|
||||
|
@ -47,6 +49,7 @@ public interface JdbcValues {
|
|||
* ^^ it's supposed to give impls a chance to write to the query cache
|
||||
* or release ResultSet it. But that could technically be handled by the
|
||||
* case of `#next` returning false the first time.
|
||||
* @param session
|
||||
*/
|
||||
void finishUp();
|
||||
void finishUp(SharedSessionContractImplementor session);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.hibernate.sql.ast.spi.SqlSelection;
|
|||
import org.hibernate.sql.exec.spi.ExecutionContext;
|
||||
import org.hibernate.sql.results.graph.Initializer;
|
||||
import org.hibernate.sql.results.graph.entity.EntityFetch;
|
||||
import org.hibernate.sql.results.spi.RowReader;
|
||||
|
||||
/**
|
||||
* State pertaining to the processing of a single "row" of a JdbcValuesSource
|
||||
|
@ -34,6 +35,11 @@ public interface RowProcessingState extends ExecutionContext {
|
|||
return getJdbcValue( sqlSelection.getValuesArrayPosition() );
|
||||
}
|
||||
|
||||
/**
|
||||
* todo (6.0) : do we want this here? Depends how we handle caching assembler / result memento
|
||||
*/
|
||||
RowReader<?> getRowReader();
|
||||
|
||||
/**
|
||||
* Retrieve the value corresponding to the given index as part
|
||||
* of the "current JDBC row".
|
||||
|
|
|
@ -90,7 +90,7 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
|
|||
finally {
|
||||
rowReader.finishUp( jdbcValuesSourceProcessingState );
|
||||
jdbcValuesSourceProcessingState.finishUp();
|
||||
jdbcValues.finishUp();
|
||||
jdbcValues.finishUp( session );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.orm.test.query.named.resultmapping;
|
||||
|
||||
import javax.persistence.ColumnResult;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.SqlResultSetMapping;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
@SqlResultSetMapping(
|
||||
name = "name",
|
||||
columns = @ColumnResult( name = "name" )
|
||||
)
|
||||
public class SimpleEntityWithNamedMappings {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
protected SimpleEntityWithNamedMappings() {
|
||||
}
|
||||
|
||||
public SimpleEntityWithNamedMappings(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.orm.test.query.named.resultmapping;
|
||||
|
||||
import org.hibernate.query.named.NamedQueryRepository;
|
||||
import org.hibernate.query.named.NamedResultSetMappingMemento;
|
||||
import org.hibernate.query.spi.QueryEngine;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.FailureExpected;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel( annotatedClasses = SimpleEntityWithNamedMappings.class )
|
||||
@SessionFactory
|
||||
public class SimpleNamedMappingTests {
|
||||
@Test
|
||||
@FailureExpected( reason = "Memento-ization of row-reader not yet implemented" )
|
||||
public void testMapping(SessionFactoryScope sessionFactoryScope) {
|
||||
final QueryEngine queryEngine = sessionFactoryScope.getSessionFactory().getQueryEngine();
|
||||
final NamedQueryRepository namedQueryRepository = queryEngine.getNamedQueryRepository();
|
||||
final NamedResultSetMappingMemento mappingMemento = namedQueryRepository.getResultSetMappingMemento( "name" );
|
||||
assertThat( mappingMemento.toResultSetMapping(), notNullValue() );
|
||||
}
|
||||
|
||||
// todo (6.0) : atm that ^^ is as far as we can test until we implement native-query support to test applying a mapping
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.orm.test.query.resultcache;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
public class AggregateEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
@ManyToOne(cascade = CascadeType.ALL)
|
||||
private TestEntity value1;
|
||||
@ManyToOne(cascade = CascadeType.ALL)
|
||||
private TestEntity value2;
|
||||
|
||||
protected AggregateEntity() {
|
||||
}
|
||||
|
||||
public AggregateEntity(
|
||||
Integer id,
|
||||
String name,
|
||||
TestEntity value1,
|
||||
TestEntity value2) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.value1 = value1;
|
||||
this.value2 = value2;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public TestEntity getValue1() {
|
||||
return value1;
|
||||
}
|
||||
|
||||
public void setValue1(TestEntity value1) {
|
||||
this.value1 = value1;
|
||||
}
|
||||
|
||||
public TestEntity getValue2() {
|
||||
return value2;
|
||||
}
|
||||
|
||||
public void setValue2(TestEntity value2) {
|
||||
this.value2 = value2;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* 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.orm.test.query.resultcache;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.CacheMode;
|
||||
import org.hibernate.cache.spi.SecondLevelCacheLogger;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.sql.exec.SqlExecLogger;
|
||||
import org.hibernate.sql.results.ResultsLogger;
|
||||
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@ServiceRegistry(
|
||||
settings = {
|
||||
@ServiceRegistry.Setting( name = AvailableSettings.USE_QUERY_CACHE, value = "true" )
|
||||
}
|
||||
)
|
||||
@DomainModel( annotatedClasses = { TestEntity.class, AggregateEntity.class } )
|
||||
@SessionFactory( generateStatistics = true )
|
||||
public class QueryResultCacheTests {
|
||||
private final Logger resultsLogger = Logger.getLogger( ResultsLogger.LOGGER_NAME );
|
||||
private final Logger execLogger = Logger.getLogger( SqlExecLogger.LOGGER_NAME );
|
||||
private final Logger cacheLogger = Logger.getLogger( SecondLevelCacheLogger.LOGGER_NAME );
|
||||
|
||||
private Level originalResultsLevel;
|
||||
private Level originalExecLevel;
|
||||
private Level originalCacheLevel;
|
||||
|
||||
@BeforeAll
|
||||
public void setUpLogger() {
|
||||
originalResultsLevel = resultsLogger.getLevel();
|
||||
resultsLogger.setLevel( Level.TRACE );
|
||||
|
||||
originalExecLevel = execLogger.getLevel();
|
||||
execLogger.setLevel( Level.TRACE );
|
||||
|
||||
originalCacheLevel = cacheLogger.getLevel();
|
||||
cacheLogger.setLevel( Level.TRACE );
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public void resetLogger() {
|
||||
resultsLogger.setLevel( originalResultsLevel );
|
||||
execLogger.setLevel( originalExecLevel );
|
||||
cacheLogger.setLevel( originalCacheLevel );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScalarCaching(SessionFactoryScope scope) {
|
||||
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
|
||||
statistics.clear();
|
||||
|
||||
assertThat( statistics.getPrepareStatementCount(), is( 0L ) );
|
||||
|
||||
final String hql = "select e.id, e.name from TestEntity e order by e.id";
|
||||
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
resultsLogger.debug( "First query ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" );
|
||||
|
||||
final List values = session.createQuery( hql )
|
||||
.setCacheable( true )
|
||||
.setCacheMode( CacheMode.NORMAL )
|
||||
.setCacheRegion( "scalar-region" )
|
||||
.list();
|
||||
|
||||
assertThat( statistics.getPrepareStatementCount(), is( 1L ) );
|
||||
|
||||
verifyScalarResults( values );
|
||||
}
|
||||
);
|
||||
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
resultsLogger.debug( "Second query ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" );
|
||||
|
||||
final List values = session.createQuery( hql )
|
||||
.setCacheable( true )
|
||||
.setCacheMode( CacheMode.NORMAL )
|
||||
.setCacheRegion( "scalar-region" )
|
||||
.list();
|
||||
|
||||
assertThat( statistics.getPrepareStatementCount(), is( 1L ) );
|
||||
|
||||
verifyScalarResults( values );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void verifyScalarResults(List values) {
|
||||
assertThat( values.size(), is( 2 ) );
|
||||
final Object[] firstRow = (Object[]) values.get( 0 );
|
||||
assertThat( firstRow[0], is( 1 ) );
|
||||
assertThat( firstRow[1], is( "first" ) );
|
||||
final Object[] secondRow = (Object[]) values.get( 1 );
|
||||
assertThat( secondRow[0], is( 2 ) );
|
||||
assertThat( secondRow[1], is( "second" ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJoinFetchCaching(SessionFactoryScope scope) {
|
||||
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
|
||||
statistics.clear();
|
||||
|
||||
assertThat( statistics.getPrepareStatementCount(), is( 0L ) );
|
||||
|
||||
final String hql = "select e from AggregateEntity e join fetch e.value1 join fetch e.value2";
|
||||
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
resultsLogger.debug( "First query ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" );
|
||||
|
||||
final List<AggregateEntity> values = session.createQuery( hql, AggregateEntity.class )
|
||||
.setCacheable( true )
|
||||
.setCacheMode( CacheMode.NORMAL )
|
||||
.setCacheRegion( "fetch-region" )
|
||||
.list();
|
||||
|
||||
assertThat( statistics.getPrepareStatementCount(), is( 1L ) );
|
||||
|
||||
verifyFetchResults( values );
|
||||
}
|
||||
);
|
||||
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
resultsLogger.debug( "Second query ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" );
|
||||
|
||||
final List<AggregateEntity> values = session.createQuery( hql, AggregateEntity.class )
|
||||
.setCacheable( true )
|
||||
.setCacheMode( CacheMode.NORMAL )
|
||||
.setCacheRegion( "fetch-region" )
|
||||
.list();
|
||||
|
||||
assertThat( statistics.getPrepareStatementCount(), is( 1L ) );
|
||||
|
||||
verifyFetchResults( values );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void verifyFetchResults(List<AggregateEntity> values) {
|
||||
assertThat( values.size(), is( 1 ) );
|
||||
final AggregateEntity rootEntity = values.get( 0 );
|
||||
assertThat( rootEntity.getValue1(), notNullValue() );
|
||||
assertThat( rootEntity.getValue1().getId(), is( 1 ) );
|
||||
assertThat( rootEntity.getValue2(), notNullValue() );
|
||||
assertThat( rootEntity.getValue2().getId(), is( 2 ) );
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void prepareTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> session.save(
|
||||
new AggregateEntity(
|
||||
1,
|
||||
"aggregate",
|
||||
new TestEntity( 1, "first" ),
|
||||
new TestEntity( 2, "second" )
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanupTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
session.createQuery( "delete AggregateEntity" ).executeUpdate();
|
||||
session.createQuery( "delete TestEntity" ).executeUpdate();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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.orm.test.query.resultcache;
|
||||
|
||||
import java.time.Instant;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
public class TestEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
@Temporal( TemporalType.TIMESTAMP )
|
||||
private Instant instant;
|
||||
|
||||
public TestEntity() {
|
||||
}
|
||||
|
||||
public TestEntity(Integer id, String name) {
|
||||
this( id, name, Instant.now() );
|
||||
}
|
||||
|
||||
public TestEntity(Integer id, String name, Instant instant) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.instant = instant;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Instant getInstant() {
|
||||
return instant;
|
||||
}
|
||||
|
||||
public void setInstant(Instant instant) {
|
||||
this.instant = instant;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue