diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/BasicValuedMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/BasicValuedMapping.java index 607f1a287a..ab43d54c34 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/BasicValuedMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/BasicValuedMapping.java @@ -56,7 +56,13 @@ public interface BasicValuedMapping extends ValueMapping, SqlExpressible { } @Override - default void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedSessionContractImplementor session){ + default void addToCacheKey( + MutableCacheKeyBuilder cacheKey, + Object value, + SharedSessionContractImplementor session) { + if ( value == null ) { + return; + } final JdbcMapping jdbcMapping = getJdbcMapping(); final BasicValueConverter converter = jdbcMapping.getValueConverter(); final Serializable disassemble; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java index c3b96da60c..954cf7dd78 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java @@ -320,6 +320,9 @@ public class IdClassEmbeddable extends AbstractEmbeddableMapping implements Iden @Override public void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedSessionContractImplementor session) { + if ( value == null ) { + return; + } final Serializable[] result = new Serializable[ getNumberOfAttributeMappings() ]; for ( int i = 0; i < result.length; i++ ) { final AttributeMapping attributeMapping = getAttributeMapping( i ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java index 7a0f3d555c..05cea13674 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java @@ -423,6 +423,9 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa @Override public void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedSessionContractImplementor session) { + if ( value == null ) { + return; + } final JdbcMapping jdbcMapping = getJdbcMapping(); final BasicValueConverter converter = jdbcMapping.getValueConverter(); final Serializable disassemble; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/TupleMappingModelExpressible.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/TupleMappingModelExpressible.java index 9dd6b8974c..1f7abdc344 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/TupleMappingModelExpressible.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/TupleMappingModelExpressible.java @@ -44,6 +44,9 @@ public class TupleMappingModelExpressible implements MappingModelExpressible { @Override public void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedSessionContractImplementor session) { + if ( value == null ) { + return; + } for ( int i = 0; i < components.length; i++ ) { components[i].addToCacheKey( cacheKey, value, session ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java index 7789856156..a1ff5335db 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java @@ -317,6 +317,9 @@ public class AnonymousTupleBasicValuedModelPart implements ModelPart, MappingTyp @Override public void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedSessionContractImplementor session) { + if ( value == null ) { + return; + } cacheKey.addValue( value ); cacheKey.addHashCode( ( (JavaType) getExpressibleJavaType() ).extractHashCode( value ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/JdbcLiteral.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/JdbcLiteral.java index 20eca823ea..bf323d1512 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/JdbcLiteral.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/JdbcLiteral.java @@ -123,6 +123,9 @@ public class JdbcLiteral implements Literal, MappingModelExpressible, Doma @Override public void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedSessionContractImplementor session) { + if ( value == null ) { + return; + } final Serializable disassemble = ( (MutabilityPlan) jdbcMapping.getJdbcJavaType().getMutabilityPlan() ) .disassemble( value, session ); final int hashCode = jdbcMapping.getJavaTypeDescriptor().extractHashCode( value ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java index e301a19a9a..307ba1bb14 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java @@ -6,6 +6,7 @@ */ package org.hibernate.sql.exec.internal; +import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -158,20 +159,27 @@ public abstract class AbstractJdbcParameter @Override public void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedSessionContractImplementor session) { + if ( value == null ) { + return; + } final JdbcMapping jdbcMapping = getJdbcMapping(); final BasicValueConverter converter = jdbcMapping.getValueConverter(); + final Serializable disassemble; + final int hashCode; if ( converter == null ) { final JavaType javaTypeDescriptor = jdbcMapping.getJavaTypeDescriptor(); - cacheKey.addValue( javaTypeDescriptor.getMutabilityPlan().disassemble( value, session ) ); - cacheKey.addHashCode( javaTypeDescriptor.extractHashCode( value ) ); + disassemble = javaTypeDescriptor.getMutabilityPlan().disassemble( value, session ); + hashCode = javaTypeDescriptor.extractHashCode( value ); } else { final Object relationalValue = converter.toRelationalValue( value ); final JavaType relationalJavaType = converter.getRelationalJavaType(); - cacheKey.addValue( relationalJavaType.getMutabilityPlan().disassemble( relationalValue, session ) ); - cacheKey.addHashCode( relationalJavaType.extractHashCode( relationalValue ) ); + disassemble = relationalJavaType.getMutabilityPlan().disassemble( relationalValue, session ); + hashCode = relationalJavaType.extractHashCode( relationalValue ); } + cacheKey.addValue( disassemble ); + cacheKey.addHashCode( hashCode ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/CustomType.java b/hibernate-core/src/main/java/org/hibernate/type/CustomType.java index 2805e00c84..1d7ad211b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CustomType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CustomType.java @@ -220,6 +220,9 @@ public class CustomType @Override public void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedSessionContractImplementor session) { + if ( value == null ) { + return; + } final Serializable disassembled = getUserType().disassemble( (J) value ); // Since UserType#disassemble is an optional operation, // we have to handle the fact that it could produce a null value, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cache/QueryCacheWithObjectParameterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cache/QueryCacheWithObjectParameterTest.java index c6ef700960..fb7ffa91bb 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cache/QueryCacheWithObjectParameterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cache/QueryCacheWithObjectParameterTest.java @@ -95,6 +95,44 @@ public class QueryCacheWithObjectParameterTest { ); } + @Test + public void testQueryWithEmbeddableParameterWithANull(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + evictQueryRegion( session ); + Query queryParent = session.createQuery( + "from Parent p where p.address = :address", + Parent.class + ); + queryParent.setParameter( "address", new Address( "via Milano", null ) ); + queryParent.setCacheable( true ); + + List resultList = queryParent.getResultList(); + assertThat( resultList ).hasSize( 0 ); + + CacheRegionStatistics defaultQueryCacheRegionStatistics = getQueryCacheRegionStatistics( session ); + assertThat( defaultQueryCacheRegionStatistics.getHitCount() ).isEqualTo( 0 ); + } + ); + + scope.inTransaction( + session -> { + Query queryParent = session.createQuery( + "from Parent p where p.address = :address", + Parent.class + ); + queryParent.setParameter( "address", new Address( "via Milano", null ) ); + queryParent.setCacheable( true ); + + List resultList = queryParent.getResultList(); + assertThat( resultList ).hasSize( 0 ); + + CacheRegionStatistics defaultQueryCacheRegionStatistics = getQueryCacheRegionStatistics( session ); + assertThat( defaultQueryCacheRegionStatistics.getHitCount() ).isEqualTo( 1 ); + } + ); + } + @Test public void testQueryCacheHits(SessionFactoryScope scope) { scope.inTransaction( @@ -189,6 +227,56 @@ public class QueryCacheWithObjectParameterTest { ); } + @Test + public void testQueryCacheHitsNullParameter(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + evictQueryRegion( session ); + Query queryParent = session.createQuery( + "from Parent p where p.name = 'John'", + Parent.class + ); + List p = queryParent.getResultList(); + assertThat( p ).hasSize( 1 ); + + Query queryChildren = session.createQuery( + "from Child c where c.parent.id = ?1", + Child.class + ); + queryChildren.setParameter( 1, null ); + queryChildren.setCacheable( true ); + List c = queryChildren.getResultList(); + assertThat( c ).hasSize( 0 ); + + CacheRegionStatistics defaultQueryCacheRegionStatistics = getQueryCacheRegionStatistics( session ); + assertThat( defaultQueryCacheRegionStatistics.getHitCount() ).isEqualTo( 0 ); + } + ); + + scope.inTransaction( + session -> { + Query queryParent = session.createQuery( + "from Parent p where p.name = 'John'", + Parent.class + ); + List p = queryParent.getResultList(); + assertThat( p ).hasSize( 1 ); + + Query queryChildren = session.createQuery( + "from Child c where c.parent.id = ?1", + Child.class + ); + queryChildren.setParameter( 1, null ); + queryChildren.setCacheable( true ); + List c = queryChildren.getResultList(); + assertThat( c ).hasSize( 0 ); + + CacheRegionStatistics defaultQueryCacheRegionStatistics = getQueryCacheRegionStatistics( session ); + assertThat( defaultQueryCacheRegionStatistics.getHitCount() ).isEqualTo( 1 ); + } + ); + } + private static void evictQueryRegion(SessionImplementor session) { session.getSessionFactory() .getCache()