HHH-16092 Trim allocation size of CacheKeyImplementation, avoid Objects::deepEquals

This commit is contained in:
Sanne Grinovero 2023-01-24 21:56:43 +00:00 committed by Sanne Grinovero
parent 4ca5902672
commit 1652102c1a
4 changed files with 161 additions and 24 deletions

View File

@ -0,0 +1,91 @@
/*
* 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;
import java.util.Objects;
import org.hibernate.Internal;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.type.Type;
/**
* Key produced by DefaultCacheKeysFactory; this is the specialized implementation
* for the case in which the disassembled identifier is not an array and the tenantId
* is not being defined.
* The goal of this specialized representation is to be more efficient for this most common
* scenario, and save memory by omitting some fields.
* When making changes to this class, please be aware of its memory footprint.
*
* @author Sanne Grinovero
* @since 6.2
*/
@Internal
final class BasicCacheKeyImplementation implements Serializable {
final Serializable id;
private final String entityOrRoleName;
private final int hashCode;
/**
* Being an internal contract the arguments are not being checked.
* @param originalId
* @param disassembledKey this must be the "disassembled" form of an ID
* @param type
* @param entityOrRoleName
*/
@Internal
public BasicCacheKeyImplementation(
final Object originalId,
final Serializable disassembledKey,
final Type type,
final String entityOrRoleName) {
assert disassembledKey != null;
assert entityOrRoleName != null;
this.id = disassembledKey;
this.entityOrRoleName = entityOrRoleName;
this.hashCode = calculateHashCode( originalId, type );
}
private static int calculateHashCode(Object disassembledKey, Type type) {
return type.getHashCode( disassembledKey );
}
public Object getId() {
return id;
}
@Override
public boolean equals(final Object other) {
if ( other == null ) {
return false;
}
else if ( this == other ) {
return true;
}
else if ( other.getClass() != BasicCacheKeyImplementation.class ) {
return false;
}
else {
final BasicCacheKeyImplementation o = (BasicCacheKeyImplementation) other;
return this.id.equals( o.id ) &&
this.entityOrRoleName.equals( o.entityOrRoleName );
}
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public String toString() {
// Used to be required for OSCache
return entityOrRoleName + '#' + id.toString();
}
}

View File

@ -22,6 +22,7 @@ import org.hibernate.type.Type;
*
* @author Gavin King
* @author Steve Ebersole
* @author Sanne Grinovero
*/
@Internal
public final class CacheKeyImplementation implements Serializable {
@ -30,28 +31,35 @@ public final class CacheKeyImplementation implements Serializable {
private final String tenantId;
private final int hashCode;
//because of object alignmnet, we had "free space" in this key:
//this field isn't strictly necessary but convenient: watch for
//class layout changes.
private final boolean requiresDeepEquals;
/**
* Construct a new key for a collection or entity instance.
* Note that an entity name should always be the root entity
* name, not a subclass entity name.
*
* @param id The identifier associated with the cached data
* @param disassembledKey
* @param type The Hibernate type mapping
* @param entityOrRoleName The entity or collection-role name.
* @param tenantId The tenant identifier associated with this data.
* @param factory The session factory for which we are caching
*/
@Internal
public CacheKeyImplementation(
final Object id,
final Serializable disassembledKey,
final Type type,
final String entityOrRoleName,
final String tenantId,
final SessionFactoryImplementor factory) {
this.id = type.disassemble( id, factory );
final String tenantId) {
assert entityOrRoleName != null;
this.id = disassembledKey;
this.entityOrRoleName = entityOrRoleName;
this.tenantId = tenantId;
this.tenantId = tenantId; //might actually be null
this.hashCode = calculateHashCode( id, type, tenantId );
this.requiresDeepEquals = disassembledKey.getClass().isArray();
}
private static int calculateHashCode(Object id, Type type, String tenantId) {
@ -69,17 +77,30 @@ public final class CacheKeyImplementation implements Serializable {
if ( other == null ) {
return false;
}
if ( this == other ) {
else if ( this == other ) {
return true;
}
if ( hashCode != other.hashCode() || !( other instanceof CacheKeyImplementation ) ) {
//hashCode is part of this check since it is pre-calculated and hash must match for equals to be true
else if ( other.getClass() != CacheKeyImplementation.class ) {
return false;
}
final CacheKeyImplementation that = (CacheKeyImplementation) other;
return entityOrRoleName.equals( that.entityOrRoleName )
&& Objects.deepEquals( id, that.id )
&& Objects.equals( tenantId, that.tenantId );
else {
CacheKeyImplementation o = (CacheKeyImplementation) other;
//check this first, so we can short-cut following checks in a different order
if ( requiresDeepEquals ) {
//only in this case, leverage the hashcode comparison check first;
//this is typically unnecessary, still far cheaper than the other checks we need to perform
//so it should be worth it.
return this.hashCode == o.hashCode &&
entityOrRoleName.equals( o.entityOrRoleName ) &&
Objects.equals( this.tenantId, o.tenantId ) &&
Objects.deepEquals( this.id, o.id );
}
else {
return this.id.equals( o.id ) &&
entityOrRoleName.equals( o.entityOrRoleName ) &&
( this.tenantId != null ? this.tenantId.equals( o.tenantId ) : o.tenantId == null );
}
}
}
@Override

View File

@ -6,11 +6,14 @@
*/
package org.hibernate.cache.internal;
import java.io.Serializable;
import org.hibernate.cache.spi.CacheKeysFactory;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.Type;
/**
* Second level cache providers now have the option to use custom key implementations.
@ -43,11 +46,27 @@ public class DefaultCacheKeysFactory implements CacheKeysFactory {
public static final DefaultCacheKeysFactory INSTANCE = new DefaultCacheKeysFactory();
public static Object staticCreateCollectionKey(Object id, CollectionPersister persister, SessionFactoryImplementor factory, String tenantIdentifier) {
return new CacheKeyImplementation( id, persister.getKeyType(), persister.getRole(), tenantIdentifier, factory );
final Type keyType = persister.getKeyType();
final Serializable disassembledKey = keyType.disassemble( id, factory );
final boolean idIsArray = disassembledKey.getClass().isArray();
if ( tenantIdentifier == null && ! idIsArray ) {
return new BasicCacheKeyImplementation( id, disassembledKey, keyType, persister.getRole() );
}
else {
return new CacheKeyImplementation( id, disassembledKey, keyType, persister.getRole(), tenantIdentifier );
}
}
public static Object staticCreateEntityKey(Object id, EntityPersister persister, SessionFactoryImplementor factory, String tenantIdentifier) {
return new CacheKeyImplementation( id, persister.getIdentifierType(), persister.getRootEntityName(), tenantIdentifier, factory );
final Type keyType = persister.getIdentifierType();
final Serializable disassembledKey = keyType.disassemble( id, factory );
final boolean idIsArray = disassembledKey.getClass().isArray();
if ( tenantIdentifier == null && ! idIsArray ) {
return new BasicCacheKeyImplementation( id, disassembledKey, keyType, persister.getRootEntityName() );
}
else {
return new CacheKeyImplementation( id, disassembledKey, keyType, persister.getRootEntityName(), tenantIdentifier );
}
}
public static Object staticCreateNaturalIdKey(Object naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) {
@ -55,44 +74,49 @@ public class DefaultCacheKeysFactory implements CacheKeysFactory {
}
public static Object staticGetEntityId(Object cacheKey) {
return ((CacheKeyImplementation) cacheKey).getId();
if ( cacheKey.getClass() == BasicCacheKeyImplementation.class ) {
return ( (BasicCacheKeyImplementation) cacheKey ).id;
}
else {
return ( (CacheKeyImplementation) cacheKey ).getId();
}
}
public static Object staticGetCollectionId(Object cacheKey) {
return ((CacheKeyImplementation) cacheKey).getId();
return staticGetEntityId( cacheKey );
}
public static Object staticGetNaturalIdValues(Object cacheKey) {
return ((NaturalIdCacheKey) cacheKey).getNaturalIdValues();
return ( (NaturalIdCacheKey) cacheKey ).getNaturalIdValues();
}
@Override
public Object createCollectionKey(Object id, CollectionPersister persister, SessionFactoryImplementor factory, String tenantIdentifier) {
return staticCreateCollectionKey(id, persister, factory, tenantIdentifier);
return staticCreateCollectionKey( id, persister, factory, tenantIdentifier );
}
@Override
public Object createEntityKey(Object id, EntityPersister persister, SessionFactoryImplementor factory, String tenantIdentifier) {
return staticCreateEntityKey(id, persister, factory, tenantIdentifier);
return staticCreateEntityKey( id, persister, factory, tenantIdentifier );
}
@Override
public Object createNaturalIdKey(Object naturalIdValues, EntityPersister persister, SharedSessionContractImplementor session) {
return staticCreateNaturalIdKey(naturalIdValues, persister, session);
return staticCreateNaturalIdKey( naturalIdValues, persister, session );
}
@Override
public Object getEntityId(Object cacheKey) {
return staticGetEntityId(cacheKey);
return staticGetEntityId( cacheKey );
}
@Override
public Object getCollectionId(Object cacheKey) {
return staticGetCollectionId(cacheKey);
return staticGetCollectionId( cacheKey );
}
@Override
public Object getNaturalIdValues(Object cacheKey) {
return staticGetNaturalIdValues(cacheKey);
return staticGetNaturalIdValues( cacheKey );
}
}

View File

@ -17,6 +17,7 @@ import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cache.internal.CacheKeyImplementation;
import org.hibernate.cache.internal.DefaultCacheKeysFactory;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.service.spi.ServiceRegistryImplementor;
@ -66,7 +67,7 @@ public class CacheKeyImplementationHashCodeTest {
int id,
EntityPersister persister,
SessionFactoryImplementor sfi) {
return new CacheKeyImplementation( id, persister.getIdentifierType(), persister.getRootEntityName(), null, sfi );
return (CacheKeyImplementation) DefaultCacheKeysFactory.staticCreateEntityKey( id, persister, sfi, "tenant" );
}
@Entity(name = "AnEntity")