diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyImplementation.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyImplementation.java index 93ca17dbeb..bf7e747b06 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyImplementation.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyImplementation.java @@ -26,7 +26,7 @@ import org.hibernate.type.Type; @Internal public final class CacheKeyImplementation implements Serializable { private final Object id; - private final Type type; + private final CacheKeyValueDescriptor cacheKeyValueDescriptor; private final String entityOrRoleName; private final String tenantId; private final int hashCode; @@ -50,15 +50,15 @@ public final class CacheKeyImplementation implements Serializable { final String tenantId, final SessionFactoryImplementor factory) { this.id = id; - this.type = type; + this.cacheKeyValueDescriptor = type.toCacheKeyDescriptor( factory ); this.entityOrRoleName = entityOrRoleName; this.tenantId = tenantId; - this.hashCode = calculateHashCode( type, factory ); + this.hashCode = calculateHashCode( ); } - private int calculateHashCode(Type type, SessionFactoryImplementor factory) { - int result = type.getHashCode( id, factory ); - result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0); + private int calculateHashCode() { + int result = cacheKeyValueDescriptor.getHashCode( id ); + result = 31 * result + ( tenantId != null ? tenantId.hashCode() : 0 ); return result; } @@ -80,7 +80,7 @@ public final class CacheKeyImplementation implements Serializable { } final CacheKeyImplementation that = (CacheKeyImplementation) other; return Objects.equals( entityOrRoleName, that.entityOrRoleName ) - && type.isEqual( id, that.id ) + && cacheKeyValueDescriptor.isEqual( id, that.id ) && Objects.equals( tenantId, that.tenantId ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyValueDescriptor.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyValueDescriptor.java new file mode 100644 index 0000000000..19c5d4db13 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyValueDescriptor.java @@ -0,0 +1,14 @@ +/* + * 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.cache.internal; + +import java.io.Serializable; + +public interface CacheKeyValueDescriptor extends Serializable { + int getHashCode(Object key); + boolean isEqual(Object key1, Object key2); +} diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/ComponentCacheKeyValueDescriptor.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/ComponentCacheKeyValueDescriptor.java new file mode 100644 index 0000000000..caf30a3a19 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/ComponentCacheKeyValueDescriptor.java @@ -0,0 +1,86 @@ +/* + * 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.cache.internal; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.type.Type; +import org.hibernate.usertype.CompositeUserType; + +public class ComponentCacheKeyValueDescriptor implements CacheKeyValueDescriptor { + + private SessionFactoryImplementor sessionFactory; + private NavigableRole role; + private Type[] propertyTypes; + private CompositeUserType compositeUserType; + private int propertySpan; + + public ComponentCacheKeyValueDescriptor( + SessionFactoryImplementor sessionFactory, + NavigableRole role, + Type[] propertyTypes, + CompositeUserType compositeUserType, + int propertySpan) { + this.sessionFactory = sessionFactory; + this.role = role; + this.propertyTypes = propertyTypes; + this.compositeUserType = compositeUserType; + this.propertySpan = propertySpan; + } + + @Override + public int getHashCode(Object key) { + if ( compositeUserType != null ) { + return compositeUserType.hashCode( key ); + } + int result = 17; + for ( int i = 0; i < propertySpan; i++ ) { + Object y = getPropertyValue( key, i ); + result *= 37; + if ( y != null ) { + result += propertyTypes[i].getHashCode( y ); + } + } + return result; + } + + @Override + public boolean isEqual(Object key1, Object key2) { + if ( key1 == key2 ) { + return true; + } + if ( compositeUserType != null ) { + return compositeUserType.equals( key1, key2 ); + } + // null value and empty component are considered equivalent + for ( int i = 0; i < propertySpan; i++ ) { + if ( !propertyTypes[i].isEqual( getPropertyValue( key1, i ), getPropertyValue( key2, i ) ) ) { + return false; + } + } + return true; + } + + public Object getPropertyValue(Object component, int i) { + if ( component == null ) { + return null; + } + + if ( component instanceof Object[] ) { + // A few calls to hashCode pass the property values already in an + // Object[] (ex: QueryKey hash codes for cached queries). + // It's easiest to just check for the condition here prior to + // trying reflection. + return ( (Object[]) component )[i]; + } + else { + return sessionFactory.getRuntimeMetamodels().getEmbedded( role ) + .getEmbeddableTypeDescriptor() + .getValue( component, i ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/MappingMetamodel.java b/hibernate-core/src/main/java/org/hibernate/metamodel/MappingMetamodel.java index 3ec4fafca0..47ecbfaf42 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/MappingMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/MappingMetamodel.java @@ -14,6 +14,7 @@ import java.util.stream.Stream; import org.hibernate.Incubating; import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.query.BindableType; import org.hibernate.metamodel.model.domain.EntityDomainType; @@ -91,6 +92,16 @@ public interface MappingMetamodel { */ EntityPersister getEntityDescriptor(NavigableRole name); + /** + * Get an EmbeddableMappingType based on its NavigableRole. + * + * @throws IllegalArgumentException if the role does not refer to an entity + * + * @see #findEntityDescriptor + */ + EmbeddableValuedModelPart getEmbeddableValuedModelPart(NavigableRole role); + + /** * Get an entity mapping descriptor based on its Class. * diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/RuntimeMetamodels.java b/hibernate-core/src/main/java/org/hibernate/metamodel/RuntimeMetamodels.java index ae74bb0301..01c45d6126 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/RuntimeMetamodels.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/RuntimeMetamodels.java @@ -11,6 +11,7 @@ import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.model.domain.JpaMetamodel; +import org.hibernate.metamodel.model.domain.NavigableRole; /** * Access to Hibernate's runtime metamodels which includes its domain-model (JPA impl) and its @@ -45,8 +46,12 @@ public interface RuntimeMetamodels { return getMappingMetamodel().findCollectionDescriptor( role ).getAttributeMapping(); } - // todo (6.0) : I think we might need a form of mapping-model look-up for embeddables, something like: + /** + @deprecated Use {@link #getEmbedded(NavigableRole)} instead + */ + @Deprecated EmbeddableValuedModelPart getEmbedded(String role); + EmbeddableValuedModelPart getEmbedded(NavigableRole role); default String getImportedName(String name) { return getMappingMetamodel().getImportedName( name ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/RuntimeMetamodelsImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/RuntimeMetamodelsImpl.java index b77608b38f..6814d234e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/RuntimeMetamodelsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/RuntimeMetamodelsImpl.java @@ -11,6 +11,7 @@ import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl; import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; @@ -41,6 +42,11 @@ public class RuntimeMetamodelsImpl implements RuntimeMetamodelsImplementor { throw new NotYetImplementedFor6Exception( getClass() ); } + @Override + public EmbeddableValuedModelPart getEmbedded(NavigableRole role) { + return mappingMetamodel.getEmbeddableValuedModelPart( role ); + } + /** * Chicken-and-egg because things try to use the SessionFactory (specifically the MappingMetamodel) * before it is ready. So we do this fugly code... diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/NavigableRole.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/NavigableRole.java index 21934dd4b8..5252e4c5ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/NavigableRole.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/NavigableRole.java @@ -6,6 +6,7 @@ */ package org.hibernate.metamodel.model.domain; +import java.io.Serializable; import java.util.Objects; import org.hibernate.spi.DotIdentifierSequence; @@ -21,7 +22,7 @@ import org.hibernate.spi.NavigablePath; * * @author Steve Ebersole */ -public class NavigableRole implements DotIdentifierSequence { +public class NavigableRole implements DotIdentifierSequence, Serializable { public static final String IDENTIFIER_MAPPER_PROPERTY = NavigablePath.IDENTIFIER_MAPPER_PROPERTY; private final NavigableRole parent; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java index 010b1d8ea1..c7bf53ca27 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java @@ -49,6 +49,7 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.internal.JpaMetaModelPopulationSetting; import org.hibernate.metamodel.internal.JpaStaticMetaModelPopulationSetting; +import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.metamodel.model.domain.BasicDomainType; @@ -73,6 +74,7 @@ import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.type.BasicType; +import org.hibernate.type.ComponentType; import org.hibernate.type.Type; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; @@ -116,7 +118,7 @@ public class MappingMetamodelImpl implements MappingMetamodelImplementor, Metamo private final Map collectionPersisterMap = new ConcurrentHashMap<>(); private final Map> collectionRolesByEntityParticipant = new ConcurrentHashMap<>(); - + private final Map embeddableValuedModelPart = new ConcurrentHashMap<>(); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // DomainMetamodel @@ -236,6 +238,7 @@ public class MappingMetamodelImpl implements MappingMetamodelImplementor, Metamo collectionPersisterMap.values().forEach( CollectionPersister::postInstantiate ); + registerEmbeddableMappingType( bootModel ); ( (JpaMetamodelImpl) this.jpaMetamodel ).processJpa( bootModel, @@ -248,6 +251,18 @@ public class MappingMetamodelImpl implements MappingMetamodelImplementor, Metamo ); } + private void registerEmbeddableMappingType(MetadataImplementor bootModel) { + bootModel.visitRegisteredComponents( + composite -> { + final EmbeddableValuedModelPart mappingModelPart = ((ComponentType) composite.getType()).getMappingModelPart(); + embeddableValuedModelPart.put( + mappingModelPart.getNavigableRole(), + mappingModelPart + ); + } + ); + } + private void processBootEntities( java.util.Collection entityBindings, CacheImplementor cacheImplementor, @@ -400,6 +415,15 @@ public class MappingMetamodelImpl implements MappingMetamodelImplementor, Metamo throw new NotYetImplementedFor6Exception( getClass() ); } + @Override + public EmbeddableValuedModelPart getEmbeddableValuedModelPart(NavigableRole role){ + EmbeddableValuedModelPart embeddableMappingType = embeddableValuedModelPart.get( role ); + if ( embeddableMappingType == null ) { + throw new IllegalArgumentException( "Unable to locate EmbeddableValuedModelPart: " + role ); + } + return embeddableMappingType; + } + @Override public EntityPersister findEntityDescriptor(String entityName) { return entityPersisterMap.get( entityName ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java b/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java index 8a8cfd5fa9..8a58688e69 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java @@ -21,15 +21,14 @@ import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.PropertyNotFoundException; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; -import org.hibernate.cfg.Environment; -import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.cache.internal.CacheKeyValueDescriptor; +import org.hibernate.cache.internal.ComponentCacheKeyValueDescriptor; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.mapping.Component; import org.hibernate.mapping.Property; import org.hibernate.metamodel.spi.EmbeddableInstantiator; @@ -64,7 +63,6 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen private final boolean isKey; private boolean hasNotNullProperty; - private final boolean createEmptyCompositesEnabled; private final CompositeUserType compositeUserType; private EmbeddableValuedModelPart mappingModelPart; @@ -100,11 +98,6 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen i++; } - this.createEmptyCompositesEnabled = ConfigurationHelper.getBoolean( - Environment.CREATE_EMPTY_COMPOSITES_ENABLED, - buildingContext.getBootstrapContext().getServiceRegistry().getService( ConfigurationService.class ).getSettings(), - false - ); if ( component.getTypeName() != null ) { //noinspection unchecked this.compositeUserType = (CompositeUserType) buildingContext.getBootstrapContext() @@ -708,6 +701,17 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen return result; } + @Override + public CacheKeyValueDescriptor toCacheKeyDescriptor(SessionFactoryImplementor sessionFactory) { + return new ComponentCacheKeyValueDescriptor( + sessionFactory, + mappingModelPart.getNavigableRole(), + propertyTypes, + compositeUserType, + propertySpan + ); + } + @Override public boolean isEmbedded() { return false; @@ -825,10 +829,6 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen return hasNotNullProperty; } - private boolean isCreateEmptyCompositesEnabled() { - return createEmptyCompositesEnabled; - } - @Override public Class getBindableJavaType() { return getReturnedClass(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/Type.java b/hibernate-core/src/main/java/org/hibernate/type/Type.java index 90206279b0..d66e72bbdd 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/Type.java +++ b/hibernate-core/src/main/java/org/hibernate/type/Type.java @@ -14,6 +14,7 @@ import java.util.Map; import org.hibernate.HibernateException; import org.hibernate.Internal; import org.hibernate.MappingException; +import org.hibernate.cache.internal.CacheKeyValueDescriptor; import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -34,7 +35,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; * @author Steve Ebersole */ @Internal -public interface Type extends Serializable { +public interface Type extends CacheKeyValueDescriptor { /** * Return true if the implementation is castable to {@link AssociationType}. This does not necessarily imply that * the type actually represents an association. Essentially a polymorphic version of @@ -431,4 +432,7 @@ public interface Type extends Serializable { */ boolean[] toColumnNullness(Object value, Mapping mapping); + default CacheKeyValueDescriptor toCacheKeyDescriptor(SessionFactoryImplementor sessionFactory) { + return this; + } }