org.hibernate.cache.spi.NaturalIdCacheKey Maven / Gradle / Ivy
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.cache.spi;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Arrays;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.ValueHolder;
import org.hibernate.internal.util.compare.EqualsHelper;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
/**
* Defines a key for caching natural identifier resolutions into the second level cache.
*
* @author Eric Dalquist
* @author Steve Ebersole
*/
public class NaturalIdCacheKey implements Serializable {
private final Serializable[] naturalIdValues;
private final String entityName;
private final String tenantId;
private final int hashCode;
// "transient" is important here -- NaturalIdCacheKey needs to be Serializable
private transient ValueHolder toString;
/**
* Construct a new key for a caching natural identifier resolutions into the second level cache.
* Note that an entity name should always be the root entity name, not a subclass entity name.
*
* @param naturalIdValues The naturalIdValues associated with the cached data
* @param persister The persister for the entity
* @param session The originating session
*/
public NaturalIdCacheKey(
final Object[] naturalIdValues,
final EntityPersister persister,
final SessionImplementor session) {
this.entityName = persister.getRootEntityName();
this.tenantId = session.getTenantIdentifier();
this.naturalIdValues = new Serializable[naturalIdValues.length];
final SessionFactoryImplementor factory = session.getFactory();
final int[] naturalIdPropertyIndexes = persister.getNaturalIdentifierProperties();
final Type[] propertyTypes = persister.getPropertyTypes();
final int prime = 31;
int result = 1;
result = prime * result + ( ( this.entityName == null ) ? 0 : this.entityName.hashCode() );
result = prime * result + ( ( this.tenantId == null ) ? 0 : this.tenantId.hashCode() );
for ( int i = 0; i < naturalIdValues.length; i++ ) {
final int naturalIdPropertyIndex = naturalIdPropertyIndexes[i];
final Type type = propertyTypes[naturalIdPropertyIndex];
final Object value = naturalIdValues[i];
result = prime * result + (value != null ? type.getHashCode( value, factory ) : 0);
// The natural id may not be fully resolved in some situations. See HHH-7513 for one of them
// (re-attaching a mutable natural id uses a database snapshot and hydration does not resolve associations).
// TODO: The snapshot should probably be revisited at some point. Consider semi-resolving, hydrating, etc.
if (type instanceof EntityType && type.getSemiResolvedType( factory ).getReturnedClass().isInstance( value )) {
this.naturalIdValues[i] = (Serializable) value;
}
else {
this.naturalIdValues[i] = type.disassemble( value, session, null );
}
}
this.hashCode = result;
initTransients();
}
private void initTransients() {
this.toString = new ValueHolder(
new ValueHolder.DeferredInitializer() {
@Override
public String initialize() {
//Complex toString is needed as naturalIds for entities are not simply based on a single value like primary keys
//the only same way to differentiate the keys is to included the disassembled values in the string.
final StringBuilder toStringBuilder = new StringBuilder( entityName ).append( "##NaturalId[" );
for ( int i = 0; i < naturalIdValues.length; i++ ) {
toStringBuilder.append( naturalIdValues[i] );
if ( i + 1 < naturalIdValues.length ) {
toStringBuilder.append( ", " );
}
}
toStringBuilder.append( "]" );
return toStringBuilder.toString();
}
}
);
}
@SuppressWarnings( {"UnusedDeclaration"})
public String getEntityName() {
return entityName;
}
@SuppressWarnings( {"UnusedDeclaration"})
public String getTenantId() {
return tenantId;
}
@SuppressWarnings( {"UnusedDeclaration"})
public Serializable[] getNaturalIdValues() {
return naturalIdValues;
}
@Override
public String toString() {
return toString.getValue();
}
@Override
public int hashCode() {
return this.hashCode;
}
@Override
public boolean equals(Object o) {
if ( o == null ) {
return false;
}
if ( this == o ) {
return true;
}
if ( hashCode != o.hashCode() || !( o instanceof NaturalIdCacheKey ) ) {
//hashCode is part of this check since it is pre-calculated and hash must match for equals to be true
return false;
}
final NaturalIdCacheKey other = (NaturalIdCacheKey) o;
return EqualsHelper.equals( entityName, other.entityName )
&& EqualsHelper.equals( tenantId, other.tenantId )
&& Arrays.deepEquals( this.naturalIdValues, other.naturalIdValues );
}
private void readObject(ObjectInputStream ois)
throws ClassNotFoundException, IOException {
ois.defaultReadObject();
initTransients();
}
}