HHH-15150 EmbeddedId CacheKeys are no longer Serializable

This commit is contained in:
Andrea Boriero 2022-04-27 11:59:46 +02:00 committed by Steve Ebersole
parent 1194e7f23b
commit 6c5870ee0b
10 changed files with 175 additions and 24 deletions

View File

@ -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,14 +50,14 @@ 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 );
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 );
}

View File

@ -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);
}

View File

@ -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<Object> compositeUserType;
private int propertySpan;
public ComponentCacheKeyValueDescriptor(
SessionFactoryImplementor sessionFactory,
NavigableRole role,
Type[] propertyTypes,
CompositeUserType<Object> 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 );
}
}
}

View File

@ -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.
*

View File

@ -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 );

View File

@ -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...

View File

@ -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;

View File

@ -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<String, CollectionPersister> collectionPersisterMap = new ConcurrentHashMap<>();
private final Map<String, Set<String>> collectionRolesByEntityParticipant = new ConcurrentHashMap<>();
private final Map<NavigableRole, EmbeddableValuedModelPart> 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<PersistentClass> 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 );

View File

@ -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<Object> 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<Object>) 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();

View File

@ -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;
}
}