HHH-15150 - EmbeddedId CacheKeys are no longer Serializable

This commit is contained in:
Steve Ebersole 2022-04-29 14:57:36 -05:00
parent 6c5870ee0b
commit e2258b3232
7 changed files with 121 additions and 54 deletions

View File

@ -8,6 +8,11 @@ package org.hibernate.cache.internal;
import java.io.Serializable;
/**
* Used to handle hash-code and equality check for cache key values
*
* @author Andrea Boriero
*/
public interface CacheKeyValueDescriptor extends Serializable {
int getHashCode(Object key);
boolean isEqual(Object key1, Object key2);

View File

@ -6,45 +6,47 @@
*/
package org.hibernate.cache.internal;
import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.type.descriptor.java.JavaType;
/**
* CacheKeyValueDescriptor used to describe normal composite mappings
*
* @see CustomComponentCacheKeyValueDescriptor
*/
public class ComponentCacheKeyValueDescriptor implements CacheKeyValueDescriptor {
private final NavigableRole role;
private final SessionFactoryImplementor sessionFactory;
private SessionFactoryImplementor sessionFactory;
private NavigableRole role;
private Type[] propertyTypes;
private CompositeUserType<Object> compositeUserType;
private int propertySpan;
private transient EmbeddableValuedModelPart embeddedMapping;
public ComponentCacheKeyValueDescriptor(
SessionFactoryImplementor sessionFactory,
NavigableRole role,
Type[] propertyTypes,
CompositeUserType<Object> compositeUserType,
int propertySpan) {
this.sessionFactory = sessionFactory;
public ComponentCacheKeyValueDescriptor(NavigableRole role, SessionFactoryImplementor sessionFactory) {
this.role = role;
this.propertyTypes = propertyTypes;
this.compositeUserType = compositeUserType;
this.propertySpan = propertySpan;
this.sessionFactory = sessionFactory;
}
@Override
public int getHashCode(Object key) {
if ( compositeUserType != null ) {
return compositeUserType.hashCode( key );
}
final List<AttributeMapping> attrs = getEmbeddedMapping().getEmbeddableTypeDescriptor().getAttributeMappings();
int result = 17;
for ( int i = 0; i < propertySpan; i++ ) {
Object y = getPropertyValue( key, i );
for ( int i = 0; i < attrs.size(); i++ ) {
final AttributeMapping attr = attrs.get( i );
final Object attrValue = getAttributeValue( key, i, attr );
result *= 37;
if ( y != null ) {
result += propertyTypes[i].getHashCode( y );
if ( attrValue != null ) {
//noinspection rawtypes
final JavaType javaType = attr.getJavaType();
//noinspection unchecked
result += javaType.extractHashCode( attrValue );
}
}
return result;
}
@ -53,34 +55,38 @@ public class ComponentCacheKeyValueDescriptor implements CacheKeyValueDescriptor
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 ) ) ) {
final List<AttributeMapping> attrs = getEmbeddedMapping().getEmbeddableTypeDescriptor().getAttributeMappings();
for ( int i = 0; i < attrs.size(); i++ ) {
final AttributeMapping attr = attrs.get( i );
final Object value1 = getAttributeValue( key1, i, attr );
final Object value2 = getAttributeValue( key2, i, attr );
//noinspection unchecked
final JavaType<Object> javaType = (JavaType<Object>) attr.getJavaType();
if ( ! javaType.areEqual( value1, value2 ) ) {
return false;
}
}
return true;
}
public Object getPropertyValue(Object component, int i) {
if ( component == null ) {
return null;
}
public Object getAttributeValue(Object component, int i, AttributeMapping attr) {
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 );
return attr.getValue( component );
}
}
private EmbeddableValuedModelPart getEmbeddedMapping() {
if ( embeddedMapping == null ) {
embeddedMapping = sessionFactory.getRuntimeMetamodels().getEmbedded( role );
}
return embeddedMapping;
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.metamodel.model.domain.NavigableRole;
import org.hibernate.usertype.CompositeUserType;
/**
* CacheKeyValueDescriptor used to describe CompositeUserType mappings
*/
public class CustomComponentCacheKeyValueDescriptor implements CacheKeyValueDescriptor {
private final NavigableRole role;
private final CompositeUserType<Object> compositeUserType;
public CustomComponentCacheKeyValueDescriptor(
NavigableRole role,
CompositeUserType<Object> compositeUserType) {
this.role = role;
this.compositeUserType = compositeUserType;
}
@Override
public int getHashCode(Object key) {
return compositeUserType.hashCode( key );
}
@Override
public boolean isEqual(Object key1, Object key2) {
return compositeUserType.equals( key1, key2 );
}
@Override
public String toString() {
return "CustomComponentCacheKeyValueDescriptor(" + role + ")";
}
}

View File

@ -9,6 +9,7 @@ package org.hibernate.type;
import java.util.Collections;
import java.util.List;
import org.hibernate.cache.internal.CacheKeyValueDescriptor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
@ -25,7 +26,7 @@ import org.hibernate.type.descriptor.java.JavaType;
*
* @author Steve Ebersole
*/
public interface BasicType<T> extends Type, BasicDomainType<T>, MappingType, BasicValuedMapping, JdbcMapping {
public interface BasicType<T> extends Type, BasicDomainType<T>, MappingType, BasicValuedMapping, JdbcMapping, CacheKeyValueDescriptor {
/**
* Get the names under which this type should be registered in the type registry.
*

View File

@ -23,6 +23,7 @@ import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.cache.internal.CacheKeyValueDescriptor;
import org.hibernate.cache.internal.ComponentCacheKeyValueDescriptor;
import org.hibernate.cache.internal.CustomComponentCacheKeyValueDescriptor;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -31,9 +32,9 @@ import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
@ -66,6 +67,7 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
private final CompositeUserType<Object> compositeUserType;
private EmbeddableValuedModelPart mappingModelPart;
private CacheKeyValueDescriptor cacheKeyValueDescriptor;
public ComponentType(Component component, int[] originalPropertyOrder, MetadataBuildingContext buildingContext) {
this.componentClass = component.isDynamic()
@ -703,13 +705,22 @@ public class ComponentType extends AbstractType implements CompositeTypeImplemen
@Override
public CacheKeyValueDescriptor toCacheKeyDescriptor(SessionFactoryImplementor sessionFactory) {
return new ComponentCacheKeyValueDescriptor(
sessionFactory,
mappingModelPart.getNavigableRole(),
propertyTypes,
compositeUserType,
propertySpan
);
if ( cacheKeyValueDescriptor == null ) {
if ( compositeUserType != null ) {
cacheKeyValueDescriptor = new CustomComponentCacheKeyValueDescriptor(
mappingModelPart.getNavigableRole(),
compositeUserType
);
}
else {
cacheKeyValueDescriptor = new ComponentCacheKeyValueDescriptor(
mappingModelPart.getNavigableRole(),
sessionFactory
);
}
}
return cacheKeyValueDescriptor;
}
@Override

View File

@ -35,7 +35,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
* @author Steve Ebersole
*/
@Internal
public interface Type extends CacheKeyValueDescriptor {
public interface Type extends Serializable {
/**
* 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
@ -433,6 +433,10 @@ public interface Type extends CacheKeyValueDescriptor {
boolean[] toColumnNullness(Object value, Mapping mapping);
default CacheKeyValueDescriptor toCacheKeyDescriptor(SessionFactoryImplementor sessionFactory) {
return this;
if ( this instanceof CacheKeyValueDescriptor ) {
return (CacheKeyValueDescriptor) this;
}
throw new HibernateException( "Type does not support use as a cache-key" );
}
}

View File

@ -25,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Gail Badner
* @author Andrea Boriero
*/
@BaseUnitTest
public class CacheKeySerializationTest {