HHH-6974 Complete second level caching of natural id resolution

This commit is contained in:
Eric Dalquist 2012-02-03 11:22:07 -06:00 committed by Steve Ebersole
parent ef22e31068
commit 1569e6194b
25 changed files with 753 additions and 221 deletions

View File

@ -97,6 +97,27 @@ public interface Cache {
*/
public void evictEntityRegions();
/**
* Evicts all naturalId data from the given region (i.e. for all entities of
* type).
*
* @param naturalIdClass The naturalId class.
*/
public void evictNaturalIdRegion(Class naturalIdClass);
/**
* Evicts all naturalId data from the given region (i.e. for all entities of
* type).
*
* @param naturalIdName The naturalId name.
*/
public void evictNaturalIdRegion(String naturalIdName);
/**
* Evict data from all naturalId regions.
*/
public void evictNaturalIdRegions();
/**
* Determine whether the cache contains data for the given collection.
* <p/>

View File

@ -24,9 +24,10 @@
package org.hibernate.cache.spi;
import java.io.Serializable;
import java.util.Arrays;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.Type;
/**
@ -38,7 +39,7 @@ import org.hibernate.type.Type;
*/
public class NaturalIdCacheKey implements Serializable {
private final Serializable[] naturalId;
private final Type[] types;
private final Type[] naturalIdTypes;
private final String entityName;
private final String tenantId;
private final int hashCode;
@ -50,54 +51,70 @@ public class NaturalIdCacheKey implements Serializable {
* name, not a subclass entity name.
*
* @param naturalId The naturalId associated with the cached data
* @param types The Hibernate type mappings
* @param entityOrRoleName The entity name.
* @param tenantId The tenant identifier associated this data.
* @param factory The session factory for which we are caching
* @param persister The persister for the entity
* @param session The session for which we are caching
*/
public NaturalIdCacheKey(
final Serializable[] naturalId,
final Type[] types,
final String entityOrRoleName,
final String tenantId,
final SessionFactoryImplementor factory) {
final Object[] naturalId,
final EntityPersister persister,
final SessionImplementor session) {
this.naturalId = naturalId;
this.types = types;
this.entityName = entityOrRoleName;
this.tenantId = tenantId;
this.entityName = persister.getEntityName();
this.tenantId = session.getTenantIdentifier();
this.hashCode = this.generateHashCode( this.naturalId, this.types, factory );
this.toString = entityOrRoleName + "##NaturalId" + Arrays.toString( this.naturalId );
}
private int generateHashCode(final Serializable[] naturalId, final Type[] types,
final SessionFactoryImplementor factory) {
final Serializable[] disassembledNaturalId = new Serializable[naturalId.length];
final Type[] naturalIdTypes = new Type[naturalId.length];
final StringBuilder str = new StringBuilder(entityName).append( "##NaturalId[" );
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 + ( ( entityName == null ) ? 0 : entityName.hashCode() );
result = prime * result + ( ( tenantId == null ) ? 0 : tenantId.hashCode() );
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 < naturalId.length; i++ ) {
result = prime * result + types[i].getHashCode( naturalId[i], factory );
final Type type = propertyTypes[naturalIdPropertyIndexes[i]];
final Object value = naturalId[i];
result = prime * result + type.getHashCode( value, factory );
disassembledNaturalId[i] = type.disassemble( value, session, null );
naturalIdTypes[i] = type;
str.append( type.toLoggableString( value, factory ) );
if (i + 1 < naturalId.length) {
str.append( ", " );
}
}
return result;
str.append( "]" );
this.naturalId = disassembledNaturalId;
this.naturalIdTypes = naturalIdTypes;
this.hashCode = result;
this.toString = str.toString();
}
@Override
public String toString() {
// Mainly for OSCache
return this.toString;
public String getEntityName() {
return entityName;
}
public String getTenantId() {
return tenantId;
}
public Serializable[] getNaturalId() {
return naturalId;
}
public Type[] getTypes() {
return types;
@Override
public String toString() {
return this.toString;
}
@Override
public int hashCode() {
return this.hashCode;
@ -112,36 +129,30 @@ public class NaturalIdCacheKey implements Serializable {
if ( getClass() != obj.getClass() )
return false;
NaturalIdCacheKey other = (NaturalIdCacheKey) obj;
if (this.hashCode != other.hashCode) {
//Short circuit on hashCode, if they aren't equal there is no chance the keys are equal
return false;
}
if ( entityName == null ) {
if ( other.entityName != null )
return false;
}
else if ( !entityName.equals( other.entityName ) )
return false;
if ( !Arrays.equals( naturalId, other.naturalId ) )
return false;
if ( tenantId == null ) {
if ( other.tenantId != null )
return false;
}
else if ( !tenantId.equals( other.tenantId ) )
return false;
for ( int i = 0; i < naturalId.length; i++ ) {
if ( !types[i].isEqual( naturalId[i], other.naturalId[i] ) ) {
if ( naturalId == other.naturalId )
return true;
if ( naturalId == null || other.naturalId == null )
return false;
int length = naturalId.length;
if ( other.naturalId.length != length )
return false;
for ( int i = 0; i < length; i++ ) {
if ( !this.naturalIdTypes[i].isEqual( naturalId[i], other.naturalId[i] ) ) {
return false;
}
}
return true;
}
public String getEntityOrRoleName() {
return entityName;
}
}

View File

@ -575,7 +575,7 @@ public final class AnnotationBinder {
entityBinder.setBatchSize( clazzToProcess.getAnnotation( BatchSize.class ) );
entityBinder.setWhere( clazzToProcess.getAnnotation( Where.class ) );
entityBinder.setCache( determineCacheSettings( clazzToProcess, mappings ) );
entityBinder.setNaturalIdCache( clazzToProcess.getAnnotation( NaturalIdCache.class ) );
entityBinder.setNaturalIdCache( clazzToProcess, clazzToProcess.getAnnotation( NaturalIdCache.class ) );
//Filters are not allowed on subclasses
if ( !inheritanceState.hasParents() ) {

View File

@ -831,14 +831,14 @@ public class EntityBinder {
}
}
public void setNaturalIdCache(NaturalIdCache naturalIdCacheAnn) {
public void setNaturalIdCache(XClass clazzToProcess, NaturalIdCache naturalIdCacheAnn) {
if ( naturalIdCacheAnn != null ) {
if ( BinderHelper.isEmptyAnnotationValue( naturalIdCacheAnn.region() ) ) {
if (cacheRegion != null) {
naturalIdCacheRegion = cacheRegion + NATURAL_ID_CACHE_SUFFIX;
}
else {
naturalIdCacheRegion = persistentClass.getEntityName() + NATURAL_ID_CACHE_SUFFIX;
naturalIdCacheRegion = clazzToProcess.getName() + NATURAL_ID_CACHE_SUFFIX;
}
}
else {

View File

@ -25,11 +25,18 @@
package org.hibernate.criterion;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.engine.spi.TypedValue;
/**
* @author Gavin King
* @deprecated Use {@link Session#byNaturalId(Class)}
* @see Session#byNaturalId(Class)
* @see Session#byNaturalId(String)
* @see Session#bySimpleNaturalId(Class)
* @see Session#bySimpleNaturalId(String)
*/
@Deprecated
public class NaturalIdentifier implements Criterion {
private Junction conjunction = new Conjunction();

View File

@ -28,6 +28,7 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import org.hibernate.Session;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.type.Type;
@ -383,6 +384,14 @@ public class Restrictions {
return new SizeExpression(propertyName, size, ">=");
}
/**
* @deprecated Use {@link Session#byNaturalId(Class)}
* @see Session#byNaturalId(Class)
* @see Session#byNaturalId(String)
* @see Session#bySimpleNaturalId(Class)
* @see Session#bySimpleNaturalId(String)
*/
@Deprecated
public static NaturalIdentifier naturalId() {
return new NaturalIdentifier();
}

View File

@ -29,6 +29,7 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
@ -62,6 +63,7 @@ import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.EntityUniqueKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.spi.EventSource;
@ -74,7 +76,6 @@ import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.tuple.ElementWrapper;
import org.hibernate.type.Type;
import org.jboss.logging.Logger;
/**
@ -1703,38 +1704,10 @@ public class StatefulPersistenceContext implements PersistenceContext {
// NATURAL ID RESOLUTION HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public void loadedStateUpdatedNotification(EntityEntry entityEntry) {
final EntityPersister persister = entityEntry.getPersister();
if ( ! persister.hasNaturalIdentifier() ) {
// nothing to do
return;
}
final Object[] naturalIdValues = getNaturalIdValues( entityEntry, persister );
// re-cache
cacheNaturalIdResolution( persister, entityEntry.getId(), naturalIdValues, CachedNaturalIdValueSource.UPDATE );
}
@Override
public void loadedStateDeletedNotification(EntityEntry entityEntry) {
final EntityPersister persister = entityEntry.getPersister();
if ( ! persister.hasNaturalIdentifier() ) {
// nothing to do
return;
}
final Object[] naturalIdValues = getNaturalIdValues( entityEntry, persister );
// evict from cache
evictNaturalIdResolution( persister, entityEntry.getId(), naturalIdValues );
}
@Override
public void loadedStateInsertedNotification(EntityEntry entityEntry) {
final EntityPersister persister = entityEntry.getPersister();
if ( ! persister.hasNaturalIdentifier() ) {
if ( !persister.hasNaturalIdentifier() ) {
// nothing to do
return;
}
@ -1744,7 +1717,35 @@ public class StatefulPersistenceContext implements PersistenceContext {
// cache
cacheNaturalIdResolution( persister, entityEntry.getId(), naturalIdValues, CachedNaturalIdValueSource.INSERT );
}
@Override
public void loadedStateUpdatedNotification(EntityEntry entityEntry) {
final EntityPersister persister = entityEntry.getPersister();
if ( !persister.hasNaturalIdentifier() ) {
// nothing to do
return;
}
final Object[] naturalIdValues = getNaturalIdValues( entityEntry, persister );
// re-cache
cacheNaturalIdResolution( persister, entityEntry.getId(), naturalIdValues, CachedNaturalIdValueSource.UPDATE );
}
@Override
public void loadedStateDeletedNotification(EntityEntry entityEntry) {
final EntityPersister persister = entityEntry.getPersister();
if ( !persister.hasNaturalIdentifier() ) {
// nothing to do
return;
}
final Object[] naturalIdValues = getNaturalIdValues( entityEntry, persister );
// evict from cache
evictNaturalIdResolution( persister, entityEntry.getId(), naturalIdValues );
}
private Object[] getNaturalIdValues(EntityEntry entityEntry, EntityPersister persister) {
final int[] naturalIdPropertyIndexes = persister.getNaturalIdentifierProperties();
final Object[] naturalIdValues = new Object[naturalIdPropertyIndexes.length];
@ -1757,24 +1758,53 @@ public class StatefulPersistenceContext implements PersistenceContext {
return naturalIdValues;
}
private NaturalIdCacheKey getNaturalIdCacheKey(Object[] naturalIdValues, EntityPersister persister) {
final int[] naturalIdPropertyIndexes = persister.getNaturalIdentifierProperties();
private static class LocalNaturalIdCacheKey {
private final EntityPersister persister;
private final Object[] values;
private int hashCode;
final Serializable[] naturalIdKeyValues = new Serializable[naturalIdPropertyIndexes.length];
final Type[] naturalIdTypes = new Type[naturalIdPropertyIndexes.length];
public LocalNaturalIdCacheKey(EntityPersister persister, Object[] values) {
this.persister = persister;
this.values = values;
final Type[] propertyTypes = persister.getPropertyTypes();
for ( int i = 0; i < naturalIdPropertyIndexes.length; i++ ) {
naturalIdTypes[i] = propertyTypes[naturalIdPropertyIndexes[i]];
naturalIdKeyValues[i] = naturalIdTypes[i].disassemble( naturalIdValues[i], session, null );
final int prime = 31;
int result = 1;
result = prime * result + ( ( persister == null ) ? 0 : persister.hashCode() );
result = prime * result + Arrays.hashCode( values );
this.hashCode = result;
}
return new NaturalIdCacheKey( naturalIdKeyValues, naturalIdTypes, persister.getEntityName(),
session.getTenantIdentifier(), session.getFactory() );
public Object[] getValues() {
return values;
}
@Override
public int hashCode() {
return this.hashCode;
}
@Override
public boolean equals(Object obj) {
if ( this == obj )
return true;
if ( obj == null )
return false;
if ( getClass() != obj.getClass() )
return false;
LocalNaturalIdCacheKey other = (LocalNaturalIdCacheKey) obj;
if ( persister == null ) {
if ( other.persister != null )
return false;
}
else if ( !persister.equals( other.persister ) )
return false;
if ( !Arrays.equals( values, other.values ) )
return false;
return true;
}
}
private class NaturalIdResolutionCache implements Serializable {
private static class NaturalIdResolutionCache implements Serializable {
private final EntityPersister persister;
private NaturalIdResolutionCache(EntityPersister persister) {
@ -1785,13 +1815,20 @@ public class StatefulPersistenceContext implements PersistenceContext {
return persister;
}
private Map<Serializable,NaturalIdCacheKey> pkToNaturalIdMap = new ConcurrentHashMap<Serializable, NaturalIdCacheKey>();
private Map<NaturalIdCacheKey,Serializable> naturalIdToPkMap = new ConcurrentHashMap<NaturalIdCacheKey,Serializable>();
private Map<Serializable, LocalNaturalIdCacheKey> pkToNaturalIdMap = new ConcurrentHashMap<Serializable, LocalNaturalIdCacheKey>();
private Map<LocalNaturalIdCacheKey, Serializable> naturalIdToPkMap = new ConcurrentHashMap<LocalNaturalIdCacheKey, Serializable>();
}
private Map<EntityPersister,NaturalIdResolutionCache> naturalIdResolutionCacheMap
= new ConcurrentHashMap<EntityPersister, NaturalIdResolutionCache>();
private void validateNaturalId(EntityPersister persister, Object[] naturalIdValues) {
if ( !persister.hasNaturalIdentifier() ) {
throw new IllegalArgumentException( "Entity did not define a natrual-id" );
}
if ( persister.getNaturalIdentifierProperties().length != naturalIdValues.length ) {
throw new IllegalArgumentException( "Mismatch between expected number of natural-id values and found." );
}
}
private final Map<EntityPersister, NaturalIdResolutionCache> naturalIdResolutionCacheMap = new ConcurrentHashMap<EntityPersister, NaturalIdResolutionCache>();
@Override
public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk) {
@ -1799,47 +1836,154 @@ public class StatefulPersistenceContext implements PersistenceContext {
if ( entityNaturalIdResolutionCache == null ) {
return null;
}
else {
final NaturalIdCacheKey naturalIdCacheKey = entityNaturalIdResolutionCache.pkToNaturalIdMap.get( pk );
if (naturalIdCacheKey == null) {
return null;
}
final Serializable[] naturalIdKeyValues = naturalIdCacheKey.getNaturalId();
final Type[] types = naturalIdCacheKey.getTypes();
Object[] naturalIdValues = new Object[naturalIdKeyValues.length];
for (int i = 0; i < naturalIdKeyValues.length; i++) {
naturalIdValues[i] = types[i].assemble( naturalIdKeyValues[i], session, null );
}
return naturalIdValues;
final LocalNaturalIdCacheKey localNaturalIdCacheKey = entityNaturalIdResolutionCache.pkToNaturalIdMap.get( pk );
if ( localNaturalIdCacheKey == null ) {
return null;
}
return localNaturalIdCacheKey.getValues();
}
@Override
public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) {
if ( ! persister.hasNaturalIdentifier() ) {
throw new IllegalArgumentException( "Entity did not define a natrual-id" );
}
if ( persister.getNaturalIdentifierProperties().length != naturalIdValues.length ) {
throw new IllegalArgumentException( "Mismatch between expected number of natural-id values and found." );
validateNaturalId( persister, naturalIdValues );
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
Serializable pk;
final LocalNaturalIdCacheKey localNaturalIdCacheKey = new LocalNaturalIdCacheKey( persister, naturalIdValues );
if ( entityNaturalIdResolutionCache != null ) {
pk = entityNaturalIdResolutionCache.naturalIdToPkMap.get( localNaturalIdCacheKey );
// Found in session cache
if ( pk != null ) {
if ( LOG.isTraceEnabled() )
LOG.tracev(
"Resolved primary key in session cache: {0}",
MessageHelper.infoString( persister, Arrays.toString( naturalIdValues ),
session.getFactory() ) );
return pk;
}
}
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
final NaturalIdCacheKey naturalIdCacheKey = getNaturalIdCacheKey( naturalIdValues, persister );
if ( entityNaturalIdResolutionCache == null ) {
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister
.getNaturalIdCacheAccessStrategy();
return (Serializable) naturalIdCacheAccessStrategy.get( naturalIdCacheKey, session.getTimestamp() );
// Session cache miss, see if second-level caching is enabled
if ( !persister.hasNaturalIdCache() ) {
return null;
}
else {
return entityNaturalIdResolutionCache.naturalIdToPkMap.get( naturalIdCacheKey );
// Try resolution from second-level cache
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( naturalIdValues, persister, session );
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
pk = (Serializable) naturalIdCacheAccessStrategy.get( naturalIdCacheKey, session.getTimestamp() );
// Found in second-level cache, store in session cache
final SessionFactoryImplementor factory = getSession().getFactory();
if ( pk != null ) {
if ( factory.getStatistics().isStatisticsEnabled() ) {
factory.getStatisticsImplementor().naturalIdCacheHit(
naturalIdCacheAccessStrategy.getRegion().getName() );
}
if ( LOG.isTraceEnabled() )
LOG.tracev( "Resolved primary key in second-level cache: {0}",
MessageHelper.infoString( persister, naturalIdCacheKey.getNaturalId(), session.getFactory() ) );
if ( entityNaturalIdResolutionCache == null ) {
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister );
naturalIdResolutionCacheMap.put( persister, entityNaturalIdResolutionCache );
}
entityNaturalIdResolutionCache.pkToNaturalIdMap.put( pk, localNaturalIdCacheKey );
entityNaturalIdResolutionCache.naturalIdToPkMap.put( localNaturalIdCacheKey, pk );
}
else if ( factory.getStatistics().isStatisticsEnabled() ) {
factory.getStatisticsImplementor().naturalIdCacheMiss( naturalIdCacheAccessStrategy.getRegion().getName() );
}
return pk;
}
@Override
public void cacheNaturalIdResolution(EntityPersister persister, final Serializable pk, Object[] naturalIdValues,
CachedNaturalIdValueSource valueSource) {
validateNaturalId( persister, naturalIdValues );
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
if ( entityNaturalIdResolutionCache == null ) {
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister );
naturalIdResolutionCacheMap.put( persister, entityNaturalIdResolutionCache );
}
final LocalNaturalIdCacheKey localNaturalIdCacheKey = new LocalNaturalIdCacheKey( persister, naturalIdValues );
entityNaturalIdResolutionCache.pkToNaturalIdMap.put( pk, localNaturalIdCacheKey );
entityNaturalIdResolutionCache.naturalIdToPkMap.put( localNaturalIdCacheKey, pk );
//If second-level caching is enabled cache the resolution there as well
if ( persister.hasNaturalIdCache() ) {
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( naturalIdValues, persister, session );
final SessionFactoryImplementor factory = getSession().getFactory();
switch ( valueSource ) {
case LOAD: {
final boolean put = naturalIdCacheAccessStrategy.putFromLoad( naturalIdCacheKey, pk, session.getTimestamp(), null );
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
factory.getStatisticsImplementor().naturalIdCachePut(
naturalIdCacheAccessStrategy.getRegion().getName() );
}
break;
}
case INSERT: {
naturalIdCacheAccessStrategy.insert( naturalIdCacheKey, pk );
( (EventSource) this.session ).getActionQueue().registerProcess(
new AfterTransactionCompletionProcess() {
@Override
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
final boolean put = naturalIdCacheAccessStrategy.afterInsert( naturalIdCacheKey, pk );
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
factory.getStatisticsImplementor().naturalIdCachePut(
naturalIdCacheAccessStrategy.getRegion().getName() );
}
}
} );
break;
}
case UPDATE: {
final SoftLock lock = naturalIdCacheAccessStrategy.lockItem( naturalIdCacheKey, null );
naturalIdCacheAccessStrategy.update( naturalIdCacheKey, pk );
( (EventSource) this.session ).getActionQueue().registerProcess(
new AfterTransactionCompletionProcess() {
@Override
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
final boolean put = naturalIdCacheAccessStrategy.afterUpdate( naturalIdCacheKey, pk, lock );
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
factory.getStatisticsImplementor().naturalIdCachePut(
naturalIdCacheAccessStrategy.getRegion().getName() );
}
}
} );
break;
}
}
}
}
@Override
public void cacheNaturalIdResolution(EntityPersister persister, final Serializable pk, Object[] naturalIdValues, CachedNaturalIdValueSource valueSource ) {
if ( ! persister.hasNaturalIdentifier() ) {
public void evictNaturalIdResolution(EntityPersister persister, final Serializable pk, Object[] naturalIdValues) {
if ( !persister.hasNaturalIdentifier() ) {
throw new IllegalArgumentException( "Entity did not define a natural-id" );
}
if ( persister.getNaturalIdentifierProperties().length != naturalIdValues.length ) {
@ -1847,71 +1991,17 @@ public class StatefulPersistenceContext implements PersistenceContext {
}
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
if ( entityNaturalIdResolutionCache == null ) {
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister );
naturalIdResolutionCacheMap.put( persister, entityNaturalIdResolutionCache );
if ( entityNaturalIdResolutionCache != null ) {
final LocalNaturalIdCacheKey localNaturalIdCacheKey = new LocalNaturalIdCacheKey( persister,
naturalIdValues );
entityNaturalIdResolutionCache.pkToNaturalIdMap.remove( pk );
entityNaturalIdResolutionCache.naturalIdToPkMap.remove( localNaturalIdCacheKey );
}
final NaturalIdCacheKey naturalIdCacheKey = getNaturalIdCacheKey( naturalIdValues, persister );
entityNaturalIdResolutionCache.pkToNaturalIdMap.put( pk, naturalIdCacheKey );
entityNaturalIdResolutionCache.naturalIdToPkMap.put( naturalIdCacheKey, pk );
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
switch ( valueSource ) {
case LOAD: {
naturalIdCacheAccessStrategy.putFromLoad( naturalIdCacheKey, pk, session.getTimestamp(), null );
break;
if ( persister.hasNaturalIdCache() ) {
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( naturalIdValues, persister, session );
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
}
case INSERT: {
naturalIdCacheAccessStrategy.insert( naturalIdCacheKey, pk );
( (EventSource) this.session ).getActionQueue().registerProcess( new AfterTransactionCompletionProcess() {
@Override
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
naturalIdCacheAccessStrategy.afterInsert( naturalIdCacheKey, pk );
}
} );
break;
}
case UPDATE: {
final SoftLock lock = naturalIdCacheAccessStrategy.lockItem( naturalIdCacheKey, null );
naturalIdCacheAccessStrategy.update( naturalIdCacheKey, pk );
( (EventSource) this.session ).getActionQueue().registerProcess( new AfterTransactionCompletionProcess() {
@Override
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
naturalIdCacheAccessStrategy.afterUpdate( naturalIdCacheKey, pk, lock );
}
} );
break;
}
}
}
@Override
public void evictNaturalIdResolution(EntityPersister persister, final Serializable pk, Object[] naturalIdValues ) {
if ( ! persister.hasNaturalIdentifier() ) {
throw new IllegalArgumentException( "Entity did not define a natural-id" );
}
if ( persister.getNaturalIdentifierProperties().length != naturalIdValues.length ) {
throw new IllegalArgumentException( "Mismatch between expected number of natural-id values and found." );
}
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
if ( entityNaturalIdResolutionCache == null ) {
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister );
naturalIdResolutionCacheMap.put( persister, entityNaturalIdResolutionCache );
}
final NaturalIdCacheKey naturalIdCacheKey = getNaturalIdCacheKey( naturalIdValues, persister );
entityNaturalIdResolutionCache.pkToNaturalIdMap.remove( pk ); //Should I compare the value returned here with the naturalIdCacheKey parameter?
entityNaturalIdResolutionCache.naturalIdToPkMap.remove( naturalIdCacheKey );
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
}
}

View File

@ -72,36 +72,22 @@ public class DefaultResolveNaturalIdEventListener
protected Serializable resolveNaturalId(final ResolveNaturalIdEvent event) {
final EntityPersister persister = event.getEntityPersister();
if ( LOG.isTraceEnabled() ) {
LOG.trace(
"Attempting to resolve: " +
MessageHelper.infoString(
persister, event.getNaturalIdValues(), event.getSession().getFactory()
)
);
}
final boolean traceEnabled = LOG.isTraceEnabled();
if ( traceEnabled )
LOG.tracev( "Attempting to resolve: {0}",
MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory() ) );
Serializable entityId = resolveFromSessionCache( event );
Serializable entityId = resolveFromCache( event );
if ( entityId != null ) {
if ( LOG.isTraceEnabled() ) {
LOG.trace(
"Resolved object in session cache: " +
MessageHelper.infoString(
persister, event.getNaturalIdValues(), event.getSession().getFactory()
)
);
}
if ( traceEnabled )
LOG.tracev( "Resolved object in cache: {0}",
MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory() ) );
return entityId;
}
if ( LOG.isTraceEnabled() ) {
LOG.trace(
"Object not resolved in any cache: " +
MessageHelper.infoString(
persister, event.getNaturalIdValues(), event.getSession().getFactory()
)
);
}
if ( traceEnabled )
LOG.tracev( "Object not resolved in any cache: {0}",
MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory() ) );
return loadFromDatasource( event );
}
@ -111,9 +97,9 @@ public class DefaultResolveNaturalIdEventListener
*
* @param event The load event
*
* @return The entity from the session-level cache, or null.
* @return The entity from the cache, or null.
*/
protected Serializable resolveFromSessionCache(final ResolveNaturalIdEvent event) {
protected Serializable resolveFromCache(final ResolveNaturalIdEvent event) {
return event.getSession().getPersistenceContext().findCachedNaturalIdResolution(
event.getEntityPersister(),
event.getOrderedNaturalIdValues()

View File

@ -1487,6 +1487,26 @@ public final class SessionFactoryImpl
}
}
public void evictNaturalIdRegion(Class entityClass) {
evictNaturalIdRegion( entityClass.getName() );
}
public void evictNaturalIdRegion(String entityName) {
EntityPersister p = getEntityPersister( entityName );
if ( p.hasNaturalIdCache() ) {
if ( LOG.isDebugEnabled() ) {
LOG.debugf( "Evicting second-level cache: %s", p.getEntityName() );
}
p.getNaturalIdCacheAccessStrategy().evictAll();
}
}
public void evictNaturalIdRegions() {
for ( String s : entityPersisters.keySet() ) {
evictNaturalIdRegion( s );
}
}
public boolean containsCollection(String role, Serializable ownerIdentifier) {
CollectionPersister p = getCollectionPersister( role );
return p.hasCache() &&

View File

@ -3902,6 +3902,10 @@ public abstract class AbstractEntityPersister
return cacheEntryStructure;
}
public boolean hasNaturalIdCache() {
return naturalIdRegionAccessStrategy != null;
}
public NaturalIdRegionAccessStrategy getNaturalIdCacheAccessStrategy() {
return naturalIdRegionAccessStrategy;
}

View File

@ -475,6 +475,11 @@ public interface EntityPersister extends OptimisticCacheSource {
*/
public CacheEntryStructure getCacheEntryStructure();
/**
* Does this class have a natural id cache
*/
public boolean hasNaturalIdCache();
/**
* Get the NaturalId cache (optional operation)
*/

View File

@ -63,6 +63,7 @@ public final class PersisterFactoryImpl implements PersisterFactory, ServiceRegi
public static final Class[] ENTITY_PERSISTER_CONSTRUCTOR_ARGS = new Class[] {
PersistentClass.class,
EntityRegionAccessStrategy.class,
NaturalIdRegionAccessStrategy.class,
SessionFactoryImplementor.class,
Mapping.class
};

View File

@ -25,6 +25,7 @@ package org.hibernate.test.annotations.naturalid;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
@ -35,12 +36,14 @@ import javax.persistence.Version;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.NaturalIdCache;
/**
* @author Guenther Demetz
*/
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@NaturalIdCache
public class A {
@Id

View File

@ -6,11 +6,13 @@ import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.NaturalIdCache;
/**
* @author Emmanuel Bernard
*/
@Entity
@NaturalIdCache
public class Citizen {
@Id
@GeneratedValue

View File

@ -114,7 +114,7 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
}
@Test
public void testNaturalIdLoaderCached() {
public void testNaturalIdLoaderNotCached() {
saveSomeCitizens();
Session s = openSession();
@ -123,6 +123,8 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
final NaturalIdLoadAccess naturalIdLoader = s.byNaturalId( Citizen.class );
naturalIdLoader.using( "ssn", "1234" ).using( "state", france );
//NaturalId cache gets populated during entity loading, need to clear it out
sessionFactory().getCache().evictNaturalIdRegions();
Statistics stats = sessionFactory().getStatistics();
stats.setStatisticsEnabled( true );
stats.clear();
@ -147,13 +149,58 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
.getNaturalIdCachePutCount()
);
// query a second time - result should be cached
citizen = (Citizen)naturalIdLoader.load();
// cleanup
tx.rollback();
s.close();
}
@Test
public void testNaturalIdLoaderCached() {
saveSomeCitizens();
Statistics stats = sessionFactory().getStatistics();
assertEquals(
"Cache hits should be empty", 0, stats
.getNaturalIdCacheHitCount()
);
assertEquals(
"First load should be a miss", 1, stats
.getNaturalIdCacheMissCount()
);
assertEquals(
"Query result should be added to cache", 3, stats
.getNaturalIdCachePutCount()
);
Session s = openSession();
Transaction tx = s.beginTransaction();
State france = ( State ) s.load( State.class, 2 );
final NaturalIdLoadAccess naturalIdLoader = s.byNaturalId( Citizen.class );
naturalIdLoader.using( "ssn", "1234" ).using( "state", france );
//Not clearing naturalId caches, should be warm from entity loading
stats.setStatisticsEnabled( true );
stats.clear();
assertEquals(
"Cache hits should be empty", 0, stats
.getQueryCacheHitCount()
);
// first query
Citizen citizen = (Citizen)naturalIdLoader.load();
assertNotNull( citizen );
assertEquals(
"Cache hits should be empty", 1, stats
.getNaturalIdCacheHitCount()
);
assertEquals(
"First load should be a miss", 0, stats
.getNaturalIdCacheMissCount()
);
assertEquals(
"Query result should be added to cache", 0, stats
.getNaturalIdCachePutCount()
);
// cleanup
tx.rollback();

View File

@ -87,6 +87,7 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
public NoopEntityPersister(org.hibernate.mapping.PersistentClass persistentClass,
org.hibernate.cache.spi.access.EntityRegionAccessStrategy strategy,
NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy,
SessionFactoryImplementor sf,
Mapping mapping) {
throw new GoofyException(NoopEntityPersister.class);
@ -383,6 +384,11 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
return null;
}
@Override
public boolean hasNaturalIdCache() {
return false;
}
@Override
public NaturalIdRegionAccessStrategy getNaturalIdCacheAccessStrategy() {
return null;

View File

@ -47,6 +47,7 @@ public class CustomPersister implements EntityPersister {
public CustomPersister(
PersistentClass model,
EntityRegionAccessStrategy cacheAccessStrategy,
NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy,
SessionFactoryImplementor factory,
Mapping mapping) {
this.factory = factory;
@ -443,6 +444,10 @@ public class CustomPersister implements EntityPersister {
return null;
}
public boolean hasNaturalIdCache() {
return false;
}
public NaturalIdRegionAccessStrategy getNaturalIdCacheAccessStrategy() {
return null;
}

View File

@ -53,6 +53,50 @@ public class NonstopAwareNaturalIdRegionAccessStrategy implements NaturalIdRegio
this.actualStrategy = actualStrategy;
this.hibernateNonstopExceptionHandler = hibernateNonstopExceptionHandler;
}
@Override
public boolean insert(Object key, Object value) throws CacheException {
try {
return actualStrategy.insert( key, value );
}
catch ( NonStopCacheException nonStopCacheException ) {
hibernateNonstopExceptionHandler.handleNonstopCacheException( nonStopCacheException );
return false;
}
}
@Override
public boolean afterInsert(Object key, Object value) throws CacheException {
try {
return actualStrategy.afterInsert( key, value );
}
catch ( NonStopCacheException nonStopCacheException ) {
hibernateNonstopExceptionHandler.handleNonstopCacheException( nonStopCacheException );
return false;
}
}
@Override
public boolean update(Object key, Object value) throws CacheException {
try {
return actualStrategy.update( key, value );
}
catch ( NonStopCacheException nonStopCacheException ) {
hibernateNonstopExceptionHandler.handleNonstopCacheException( nonStopCacheException );
return false;
}
}
@Override
public boolean afterUpdate(Object key, Object value, SoftLock lock) throws CacheException {
try {
return actualStrategy.afterUpdate( key, value, lock );
}
catch ( NonStopCacheException nonStopCacheException ) {
hibernateNonstopExceptionHandler.handleNonstopCacheException( nonStopCacheException );
return false;
}
}
/**
* {@inheritDoc}

View File

@ -89,6 +89,36 @@ public class NonStrictReadWriteEhcacheNaturalIdRegionAccessStrategy
region.remove( key );
}
/**
* Returns <code>false</code> since this is an asynchronous cache access strategy.
*/
public boolean insert(Object key, Object value ) throws CacheException {
return false;
}
/**
* Returns <code>false</code> since this is a non-strict read/write cache access strategy
*/
public boolean afterInsert(Object key, Object value ) throws CacheException {
return false;
}
/**
* Removes the entry since this is a non-strict read/write cache strategy.
*/
public boolean update(Object key, Object value ) throws CacheException {
remove( key );
return false;
}
/**
* {@inheritDoc}
*/
public boolean afterUpdate(Object key, Object value, SoftLock lock) throws CacheException {
unlockItem( key, lock );
return false;
}
/**
* {@inheritDoc}
*/

View File

@ -90,4 +90,37 @@ public class ReadOnlyEhcacheNaturalIdRegionAccessStrategy
public void unlockItem(Object key, SoftLock lock) throws CacheException {
//throw new UnsupportedOperationException("Can't write to a readonly object");
}
/**
* This cache is asynchronous hence a no-op
*/
public boolean insert(Object key, Object value ) throws CacheException {
return false;
}
/**
* {@inheritDoc}
*/
public boolean afterInsert(Object key, Object value ) throws CacheException {
region.put( key, value );
return true;
}
/**
* Throws UnsupportedOperationException since this cache is read-only
*
* @throws UnsupportedOperationException always
*/
public boolean update(Object key, Object value ) throws UnsupportedOperationException {
throw new UnsupportedOperationException( "Can't write to a readonly object" );
}
/**
* Throws UnsupportedOperationException since this cache is read-only
*
* @throws UnsupportedOperationException always
*/
public boolean afterUpdate(Object key, Object value, SoftLock lock) throws UnsupportedOperationException {
throw new UnsupportedOperationException( "Can't write to a readonly object" );
}
}

View File

@ -23,9 +23,11 @@
*/
package org.hibernate.cache.ehcache.internal.strategy;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.ehcache.internal.regions.EhcacheNaturalIdRegion;
import org.hibernate.cache.spi.NaturalIdRegion;
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.cfg.Settings;
/**
@ -51,4 +53,75 @@ public class ReadWriteEhcacheNaturalIdRegionAccessStrategy
public NaturalIdRegion getRegion() {
return region;
}
/**
* A no-op since this is an asynchronous cache access strategy.
*/
public boolean insert(Object key, Object value ) throws CacheException {
return false;
}
/**
* {@inheritDoc}
* <p/>
* Inserts will only succeed if there is no existing value mapped to this key.
*/
public boolean afterInsert(Object key, Object value ) throws CacheException {
region.writeLock( key );
try {
Lockable item = (Lockable) region.get( key );
if ( item == null ) {
region.put( key, new Item( value, null, region.nextTimestamp() ) );
return true;
}
else {
return false;
}
}
finally {
region.writeUnlock( key );
}
}
/**
* A no-op since this is an asynchronous cache access strategy.
*/
public boolean update(Object key, Object value )
throws CacheException {
return false;
}
/**
* {@inheritDoc}
* <p/>
* Updates will only succeed if this entry was locked by this transaction and exclusively this transaction for the
* duration of this transaction. It is important to also note that updates will fail if the soft-lock expired during
* the course of this transaction.
*/
public boolean afterUpdate(Object key, Object value, SoftLock lock) throws CacheException {
//what should we do with previousVersion here?
region.writeLock( key );
try {
Lockable item = (Lockable) region.get( key );
if ( item != null && item.isUnlockable( lock ) ) {
Lock lockItem = (Lock) item;
if ( lockItem.wasLockedConcurrently() ) {
decrementLock( key, lockItem );
return false;
}
else {
region.put( key, new Item( value, null, region.nextTimestamp() ) );
return true;
}
}
else {
handleLockExpiry( key, item );
return false;
}
}
finally {
region.writeUnlock( key );
}
}
}

View File

@ -58,6 +58,20 @@ public class TransactionalEhcacheNaturalIdRegionAccessStrategy
this.ehcache = ehcache;
}
/**
* {@inheritDoc}
*/
public boolean afterInsert(Object key, Object value ) {
return false;
}
/**
* {@inheritDoc}
*/
public boolean afterUpdate(Object key, Object value, SoftLock lock) {
return false;
}
/**
* {@inheritDoc}
@ -79,6 +93,20 @@ public class TransactionalEhcacheNaturalIdRegionAccessStrategy
return region;
}
/**
* {@inheritDoc}
*/
public boolean insert(Object key, Object value ) throws CacheException {
//OptimisticCache? versioning?
try {
ehcache.put( new Element( key, value ) );
return true;
}
catch ( net.sf.ehcache.CacheException e ) {
throw new CacheException( e );
}
}
/**
* {@inheritDoc}
*/
@ -124,4 +152,17 @@ public class TransactionalEhcacheNaturalIdRegionAccessStrategy
// no-op
}
/**
* {@inheritDoc}
*/
public boolean update(Object key, Object value ) throws CacheException {
try {
ehcache.put( new Element( key, value ) );
return true;
}
catch ( net.sf.ehcache.CacheException e ) {
throw new CacheException( e );
}
}
}

View File

@ -24,6 +24,7 @@ import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import java.io.Serializable;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import org.junit.Assert;
@ -36,6 +37,7 @@ import org.hibernate.LockOptions;
import org.hibernate.MappingException;
import org.hibernate.bytecode.spi.EntityInstrumentationMetadata;
import org.hibernate.cache.spi.access.EntityRegionAccessStrategy;
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
import org.hibernate.cache.spi.entry.CacheEntryStructure;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.engine.spi.CascadeStyle;
@ -108,6 +110,7 @@ public class PersisterClassProviderTest {
public GoofyProvider(
org.hibernate.mapping.PersistentClass persistentClass,
org.hibernate.cache.spi.access.EntityRegionAccessStrategy strategy,
NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy,
SessionFactoryImplementor sf,
Mapping mapping) {
throw new GoofyException();
@ -273,8 +276,18 @@ public class PersisterClassProviderTest {
SessionImplementor session) {
return null;
}
@Override
public boolean hasNaturalIdCache() {
return false;
}
@Override
public NaturalIdRegionAccessStrategy getNaturalIdCacheAccessStrategy() {
return null;
}
@Override
public IdentifierGenerator getIdentifierGenerator() {
return null;
}

View File

@ -23,8 +23,10 @@
*/
package org.hibernate.testing.cache;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.NaturalIdRegion;
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
import org.hibernate.cache.spi.access.SoftLock;
/**
* @author Eric Dalquist
@ -47,6 +49,26 @@ class BaseNaturalIdRegionAccessStrategy extends BaseRegionAccessStrategy impleme
return region;
}
@Override
public boolean insert(Object key, Object value ) throws CacheException {
return putFromLoad( key, value, 0 , null );
}
@Override
public boolean afterInsert(Object key, Object value ) throws CacheException {
return true;
}
@Override
public boolean update(Object key, Object value ) throws CacheException {
return false;
}
@Override
public boolean afterUpdate(Object key, Object value, SoftLock lock) throws CacheException {
return false;
}
BaseNaturalIdRegionAccessStrategy(NaturalIdRegionImpl region) {
this.region = region;
}

View File

@ -25,8 +25,10 @@ package org.hibernate.testing.cache;
import java.util.Comparator;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.NaturalIdRegion;
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
import org.hibernate.cache.spi.access.SoftLock;
/**
* @author Eric Dalquist
@ -40,6 +42,63 @@ class ReadWriteNaturalIdRegionAccessStrategy extends AbstractReadWriteAccessStra
this.region = region;
}
@Override
public boolean insert(Object key, Object value ) throws CacheException {
return false;
}
@Override
public boolean update(Object key, Object value ) throws CacheException {
return false;
}
@Override
public boolean afterInsert(Object key, Object value ) throws CacheException {
try {
writeLock.lock();
Lockable item = (Lockable) region.get( key );
if ( item == null ) {
region.put( key, new Item( value, null, region.nextTimestamp() ) );
return true;
}
else {
return false;
}
}
finally {
writeLock.unlock();
}
}
@Override
public boolean afterUpdate(Object key, Object value, SoftLock lock) throws CacheException {
try {
writeLock.lock();
Lockable item = (Lockable) region.get( key );
if ( item != null && item.isUnlockable( lock ) ) {
Lock lockItem = (Lock) item;
if ( lockItem.wasLockedConcurrently() ) {
decrementLock( key, lockItem );
return false;
}
else {
region.put( key, new Item( value, null, region.nextTimestamp() ) );
return true;
}
}
else {
handleLockExpiry( key, item );
return false;
}
}
finally {
writeLock.unlock();
}
}
@Override
Comparator getVersionComparator() {
return region.getCacheDataDescription().getVersionComparator();