@@ -263,6 +263,7 @@ private JdbcValues resolveJdbcValuesSource(
263263
264264 final QueryKey queryResultsCacheKey ;
265265 final List <?> cachedResults ;
266+ final String queryCacheRegionName ;
266267 if ( cacheable && cacheMode .isGetEnabled () ) {
267268 SQL_EXEC_LOGGER .tracef ( "Reading query result cache data [%s]" , cacheMode .name () );
268269 final Set <String > querySpaces = jdbcSelect .getAffectedTableNames ();
@@ -273,9 +274,9 @@ private JdbcValues resolveJdbcValuesSource(
273274 SQL_EXEC_LOGGER .tracef ( "Affected query spaces %s" , querySpaces );
274275 }
275276
276- final var queryCache =
277- factory . getCache ()
278- . getQueryResultsCache ( queryOptions . getResultCacheRegionName () );
277+ final var queryCache = factory . getCache ()
278+ . getQueryResultsCache ( queryOptions . getResultCacheRegionName () );
279+ queryCacheRegionName = queryCache . getRegion (). getName ( );
279280
280281 queryResultsCacheKey = QueryKey .from (
281282 jdbcSelect .getSqlString (),
@@ -299,23 +300,14 @@ private JdbcValues resolveJdbcValuesSource(
299300 //
300301 // todo (6.0) : if we go this route (^^), still beneficial to have an abstraction over different UpdateTimestampsCache-based
301302 // invalidation strategies - QueryCacheInvalidationStrategy
302-
303- final var statistics = factory .getStatistics ();
304- if ( statistics .isStatisticsEnabled () ) {
305- if ( cachedResults == null ) {
306- statistics .queryCacheMiss ( queryIdentifier , queryCache .getRegion ().getName () );
307- }
308- else {
309- statistics .queryCacheHit ( queryIdentifier , queryCache .getRegion ().getName () );
310- }
311- }
312303 }
313304 else {
314305 SQL_EXEC_LOGGER .tracef ( "Skipping reading query result cache data (query cache %s, cache mode %s)" ,
315306 queryCacheEnabled ? "enabled" : "disabled" ,
316307 cacheMode .name ()
317308 );
318309 cachedResults = null ;
310+ queryCacheRegionName = null ;
319311 if ( cacheable && cacheMode .isPutEnabled () ) {
320312 queryResultsCacheKey = QueryKey .from (
321313 jdbcSelect .getSqlString (),
@@ -329,7 +321,7 @@ private JdbcValues resolveJdbcValuesSource(
329321 }
330322 }
331323
332- return resolveJdbcValues (
324+ final var result = resolveJdbcValues (
333325 queryIdentifier ,
334326 executionContext ,
335327 resultSetAccess ,
@@ -339,6 +331,20 @@ private JdbcValues resolveJdbcValuesSource(
339331 session ,
340332 factory
341333 );
334+
335+ if ( queryCacheRegionName != null ) {
336+ final var statistics = factory .getStatistics ();
337+ if ( statistics .isStatisticsEnabled () ) {
338+ if ( result instanceof JdbcValuesCacheHit ) {
339+ statistics .queryCacheHit ( queryIdentifier , queryCacheRegionName );
340+ }
341+ else {
342+ statistics .queryCacheMiss ( queryIdentifier , queryCacheRegionName );
343+ }
344+ }
345+ }
346+
347+ return result ;
342348 }
343349
344350 private static AbstractJdbcValues resolveJdbcValues (
@@ -351,39 +357,49 @@ private static AbstractJdbcValues resolveJdbcValues(
351357 SharedSessionContractImplementor session ,
352358 SessionFactoryImplementor factory ) {
353359 final var loadQueryInfluencers = session .getLoadQueryInfluencers ();
354- if ( cachedResults == null ) {
355- final CachedJdbcValuesMetadata metadataForCache ;
356- final JdbcValuesMapping jdbcValuesMapping ;
357- if ( queryResultsCacheKey == null ) {
358- jdbcValuesMapping = mappingProducer .resolve ( resultSetAccess , loadQueryInfluencers , factory );
359- metadataForCache = null ;
360+ // Try to use cached results if available
361+ if ( cachedResults != null ) {
362+ try {
363+ final var valuesMetadata =
364+ !cachedResults .isEmpty ()
365+ && cachedResults .get ( 0 ) instanceof JdbcValuesMetadata jdbcValuesMetadata
366+ ? jdbcValuesMetadata
367+ : resultSetAccess ;
368+ final var resolvedMapping =
369+ mappingProducer .resolve ( valuesMetadata , loadQueryInfluencers , factory );
370+ final var cacheHit = new JdbcValuesCacheHit ( cachedResults , resolvedMapping );
371+ if ( cacheHit .isCacheCompatible () ) {
372+ return cacheHit ;
373+ }
374+ // Cached data incompatible with the resolved mapping — fall through to re-execute
360375 }
361- else {
362- // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata
363- final var capturingMetadata = new CapturingJdbcValuesMetadata ( resultSetAccess );
364- jdbcValuesMapping = mappingProducer .resolve ( capturingMetadata , loadQueryInfluencers , factory );
365- metadataForCache = capturingMetadata .resolveMetadataForCache ();
376+ catch (CachedJdbcValuesMetadata .CacheMetadataIncompleteException e ) {
377+ // Cached metadata doesn't cover all required columns — fall through to re-execute
366378 }
367- return new JdbcValuesResultSetImpl (
368- resultSetAccess ,
369- queryResultsCacheKey ,
370- queryIdentifier ,
371- executionContext .getQueryOptions (),
372- resultSetAccess .usesFollowOnLocking (),
373- jdbcValuesMapping ,
374- metadataForCache ,
375- executionContext
376- );
379+ }
380+ // Execute query (cache miss or insufficient cached data)
381+ final CachedJdbcValuesMetadata metadataForCache ;
382+ final JdbcValuesMapping jdbcValuesMapping ;
383+ if ( queryResultsCacheKey == null ) {
384+ jdbcValuesMapping = mappingProducer .resolve ( resultSetAccess , loadQueryInfluencers , factory );
385+ metadataForCache = null ;
377386 }
378387 else {
379- final var valuesMetadata =
380- !cachedResults .isEmpty ()
381- && cachedResults .get ( 0 ) instanceof JdbcValuesMetadata jdbcValuesMetadata
382- ? jdbcValuesMetadata
383- : resultSetAccess ;
384- return new JdbcValuesCacheHit ( cachedResults ,
385- mappingProducer .resolve ( valuesMetadata , loadQueryInfluencers , factory ) );
388+ // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata
389+ final var capturingMetadata = new CapturingJdbcValuesMetadata ( resultSetAccess );
390+ jdbcValuesMapping = mappingProducer .resolve ( capturingMetadata , loadQueryInfluencers , factory );
391+ metadataForCache = capturingMetadata .resolveMetadataForCache ( jdbcValuesMapping );
386392 }
393+ return new JdbcValuesResultSetImpl (
394+ resultSetAccess ,
395+ queryResultsCacheKey ,
396+ queryIdentifier ,
397+ executionContext .getQueryOptions (),
398+ resultSetAccess .usesFollowOnLocking (),
399+ jdbcValuesMapping ,
400+ metadataForCache ,
401+ executionContext
402+ );
387403 }
388404
389405 private static CacheMode resolveCacheMode (ExecutionContext executionContext ) {
@@ -467,8 +483,23 @@ public <J> BasicType<J> resolveType(
467483 return basicType ;
468484 }
469485
470- public CachedJdbcValuesMetadata resolveMetadataForCache () {
471- return columnNames == null ? null : new CachedJdbcValuesMetadata ( columnNames , types );
486+ public CachedJdbcValuesMetadata resolveMetadataForCache (JdbcValuesMapping jdbcValuesMapping ) {
487+ if ( columnNames == null ) {
488+ return null ;
489+ }
490+ // Fill in types from the mapping's SqlSelections for positions that
491+ // were not captured during mapping resolution (e.g. entity results)
492+ for ( var selection : jdbcValuesMapping .getSqlSelections () ) {
493+ final int pos = selection .getValuesArrayPosition ();
494+ if ( types [pos ] == null && selection .getExpressionType () != null ) {
495+ types [pos ] = (BasicType <?>) selection .getExpressionType ().getSingleJdbcMapping ();
496+ }
497+ }
498+ return new CachedJdbcValuesMetadata (
499+ columnNames ,
500+ types ,
501+ jdbcValuesMapping .getValueIndexesToCacheIndexes ()
502+ );
472503 }
473504 }
474505
0 commit comments