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(); 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. * Determine whether the cache contains data for the given collection.
* <p/> * <p/>

View File

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

View File

@ -575,7 +575,7 @@ public final class AnnotationBinder {
entityBinder.setBatchSize( clazzToProcess.getAnnotation( BatchSize.class ) ); entityBinder.setBatchSize( clazzToProcess.getAnnotation( BatchSize.class ) );
entityBinder.setWhere( clazzToProcess.getAnnotation( Where.class ) ); entityBinder.setWhere( clazzToProcess.getAnnotation( Where.class ) );
entityBinder.setCache( determineCacheSettings( clazzToProcess, mappings ) ); entityBinder.setCache( determineCacheSettings( clazzToProcess, mappings ) );
entityBinder.setNaturalIdCache( clazzToProcess.getAnnotation( NaturalIdCache.class ) ); entityBinder.setNaturalIdCache( clazzToProcess, clazzToProcess.getAnnotation( NaturalIdCache.class ) );
//Filters are not allowed on subclasses //Filters are not allowed on subclasses
if ( !inheritanceState.hasParents() ) { 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 ( naturalIdCacheAnn != null ) {
if ( BinderHelper.isEmptyAnnotationValue( naturalIdCacheAnn.region() ) ) { if ( BinderHelper.isEmptyAnnotationValue( naturalIdCacheAnn.region() ) ) {
if (cacheRegion != null) { if (cacheRegion != null) {
naturalIdCacheRegion = cacheRegion + NATURAL_ID_CACHE_SUFFIX; naturalIdCacheRegion = cacheRegion + NATURAL_ID_CACHE_SUFFIX;
} }
else { else {
naturalIdCacheRegion = persistentClass.getEntityName() + NATURAL_ID_CACHE_SUFFIX; naturalIdCacheRegion = clazzToProcess.getName() + NATURAL_ID_CACHE_SUFFIX;
} }
} }
else { else {

View File

@ -25,11 +25,18 @@
package org.hibernate.criterion; package org.hibernate.criterion;
import org.hibernate.Criteria; import org.hibernate.Criteria;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.engine.spi.TypedValue; import org.hibernate.engine.spi.TypedValue;
/** /**
* @author Gavin King * @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 { public class NaturalIdentifier implements Criterion {
private Junction conjunction = new Conjunction(); private Junction conjunction = new Conjunction();

View File

@ -28,6 +28,7 @@ import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import org.hibernate.Session;
import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.type.Type; import org.hibernate.type.Type;
@ -383,6 +384,14 @@ public class Restrictions {
return new SizeExpression(propertyName, size, ">="); 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() { public static NaturalIdentifier naturalId() {
return new NaturalIdentifier(); return new NaturalIdentifier();
} }

View File

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

View File

@ -72,36 +72,22 @@ public class DefaultResolveNaturalIdEventListener
protected Serializable resolveNaturalId(final ResolveNaturalIdEvent event) { protected Serializable resolveNaturalId(final ResolveNaturalIdEvent event) {
final EntityPersister persister = event.getEntityPersister(); final EntityPersister persister = event.getEntityPersister();
if ( LOG.isTraceEnabled() ) { final boolean traceEnabled = LOG.isTraceEnabled();
LOG.trace( if ( traceEnabled )
"Attempting to resolve: " + LOG.tracev( "Attempting to resolve: {0}",
MessageHelper.infoString( MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory() ) );
persister, event.getNaturalIdValues(), event.getSession().getFactory()
)
);
}
Serializable entityId = resolveFromSessionCache( event ); Serializable entityId = resolveFromCache( event );
if ( entityId != null ) { if ( entityId != null ) {
if ( LOG.isTraceEnabled() ) { if ( traceEnabled )
LOG.trace( LOG.tracev( "Resolved object in cache: {0}",
"Resolved object in session cache: " + MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory() ) );
MessageHelper.infoString(
persister, event.getNaturalIdValues(), event.getSession().getFactory()
)
);
}
return entityId; return entityId;
} }
if ( LOG.isTraceEnabled() ) { if ( traceEnabled )
LOG.trace( LOG.tracev( "Object not resolved in any cache: {0}",
"Object not resolved in any cache: " + MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory() ) );
MessageHelper.infoString(
persister, event.getNaturalIdValues(), event.getSession().getFactory()
)
);
}
return loadFromDatasource( event ); return loadFromDatasource( event );
} }
@ -111,9 +97,9 @@ public class DefaultResolveNaturalIdEventListener
* *
* @param event The load event * @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( return event.getSession().getPersistenceContext().findCachedNaturalIdResolution(
event.getEntityPersister(), event.getEntityPersister(),
event.getOrderedNaturalIdValues() 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) { public boolean containsCollection(String role, Serializable ownerIdentifier) {
CollectionPersister p = getCollectionPersister( role ); CollectionPersister p = getCollectionPersister( role );
return p.hasCache() && return p.hasCache() &&

View File

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

View File

@ -475,6 +475,11 @@ public interface EntityPersister extends OptimisticCacheSource {
*/ */
public CacheEntryStructure getCacheEntryStructure(); public CacheEntryStructure getCacheEntryStructure();
/**
* Does this class have a natural id cache
*/
public boolean hasNaturalIdCache();
/** /**
* Get the NaturalId cache (optional operation) * 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[] { public static final Class[] ENTITY_PERSISTER_CONSTRUCTOR_ARGS = new Class[] {
PersistentClass.class, PersistentClass.class,
EntityRegionAccessStrategy.class, EntityRegionAccessStrategy.class,
NaturalIdRegionAccessStrategy.class,
SessionFactoryImplementor.class, SessionFactoryImplementor.class,
Mapping.class Mapping.class
}; };

View File

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

View File

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

View File

@ -114,7 +114,7 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
} }
@Test @Test
public void testNaturalIdLoaderCached() { public void testNaturalIdLoaderNotCached() {
saveSomeCitizens(); saveSomeCitizens();
Session s = openSession(); Session s = openSession();
@ -123,6 +123,8 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
final NaturalIdLoadAccess naturalIdLoader = s.byNaturalId( Citizen.class ); final NaturalIdLoadAccess naturalIdLoader = s.byNaturalId( Citizen.class );
naturalIdLoader.using( "ssn", "1234" ).using( "state", france ); 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(); Statistics stats = sessionFactory().getStatistics();
stats.setStatisticsEnabled( true ); stats.setStatisticsEnabled( true );
stats.clear(); stats.clear();
@ -147,13 +149,58 @@ public class NaturalIdTest extends BaseCoreFunctionalTestCase {
.getNaturalIdCachePutCount() .getNaturalIdCachePutCount()
); );
// query a second time - result should be cached // cleanup
citizen = (Citizen)naturalIdLoader.load(); 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 ); assertNotNull( citizen );
assertEquals( assertEquals(
"Cache hits should be empty", 1, stats "Cache hits should be empty", 1, stats
.getNaturalIdCacheHitCount() .getNaturalIdCacheHitCount()
); );
assertEquals(
"First load should be a miss", 0, stats
.getNaturalIdCacheMissCount()
);
assertEquals(
"Query result should be added to cache", 0, stats
.getNaturalIdCachePutCount()
);
// cleanup // cleanup
tx.rollback(); tx.rollback();

View File

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

View File

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

View File

@ -54,6 +54,50 @@ public class NonstopAwareNaturalIdRegionAccessStrategy implements NaturalIdRegio
this.hibernateNonstopExceptionHandler = hibernateNonstopExceptionHandler; 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} * {@inheritDoc}
* *

View File

@ -89,6 +89,36 @@ public class NonStrictReadWriteEhcacheNaturalIdRegionAccessStrategy
region.remove( key ); 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} * {@inheritDoc}
*/ */

View File

@ -90,4 +90,37 @@ public class ReadOnlyEhcacheNaturalIdRegionAccessStrategy
public void unlockItem(Object key, SoftLock lock) throws CacheException { public void unlockItem(Object key, SoftLock lock) throws CacheException {
//throw new UnsupportedOperationException("Can't write to a readonly object"); //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; package org.hibernate.cache.ehcache.internal.strategy;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.ehcache.internal.regions.EhcacheNaturalIdRegion; import org.hibernate.cache.ehcache.internal.regions.EhcacheNaturalIdRegion;
import org.hibernate.cache.spi.NaturalIdRegion; import org.hibernate.cache.spi.NaturalIdRegion;
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.cfg.Settings; import org.hibernate.cfg.Settings;
/** /**
@ -51,4 +53,75 @@ public class ReadWriteEhcacheNaturalIdRegionAccessStrategy
public NaturalIdRegion getRegion() { public NaturalIdRegion getRegion() {
return region; 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; 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} * {@inheritDoc}
@ -79,6 +93,20 @@ public class TransactionalEhcacheNaturalIdRegionAccessStrategy
return region; 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} * {@inheritDoc}
*/ */
@ -124,4 +152,17 @@ public class TransactionalEhcacheNaturalIdRegionAccessStrategy
// no-op // 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 javax.persistence.PersistenceException;
import java.io.Serializable; import java.io.Serializable;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.Assert; import org.junit.Assert;
@ -36,6 +37,7 @@ import org.hibernate.LockOptions;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.bytecode.spi.EntityInstrumentationMetadata; import org.hibernate.bytecode.spi.EntityInstrumentationMetadata;
import org.hibernate.cache.spi.access.EntityRegionAccessStrategy; import org.hibernate.cache.spi.access.EntityRegionAccessStrategy;
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
import org.hibernate.cache.spi.entry.CacheEntryStructure; import org.hibernate.cache.spi.entry.CacheEntryStructure;
import org.hibernate.ejb.Ejb3Configuration; import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyle;
@ -108,6 +110,7 @@ public class PersisterClassProviderTest {
public GoofyProvider( public GoofyProvider(
org.hibernate.mapping.PersistentClass persistentClass, org.hibernate.mapping.PersistentClass persistentClass,
org.hibernate.cache.spi.access.EntityRegionAccessStrategy strategy, org.hibernate.cache.spi.access.EntityRegionAccessStrategy strategy,
NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy,
SessionFactoryImplementor sf, SessionFactoryImplementor sf,
Mapping mapping) { Mapping mapping) {
throw new GoofyException(); throw new GoofyException();
@ -274,6 +277,16 @@ public class PersisterClassProviderTest {
return null; return null;
} }
@Override
public boolean hasNaturalIdCache() {
return false;
}
@Override
public NaturalIdRegionAccessStrategy getNaturalIdCacheAccessStrategy() {
return null;
}
@Override @Override
public IdentifierGenerator getIdentifierGenerator() { public IdentifierGenerator getIdentifierGenerator() {
return null; return null;

View File

@ -23,8 +23,10 @@
*/ */
package org.hibernate.testing.cache; package org.hibernate.testing.cache;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.NaturalIdRegion; import org.hibernate.cache.spi.NaturalIdRegion;
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
import org.hibernate.cache.spi.access.SoftLock;
/** /**
* @author Eric Dalquist * @author Eric Dalquist
@ -47,6 +49,26 @@ class BaseNaturalIdRegionAccessStrategy extends BaseRegionAccessStrategy impleme
return region; 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) { BaseNaturalIdRegionAccessStrategy(NaturalIdRegionImpl region) {
this.region = region; this.region = region;
} }

View File

@ -25,8 +25,10 @@ package org.hibernate.testing.cache;
import java.util.Comparator; import java.util.Comparator;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.NaturalIdRegion; import org.hibernate.cache.spi.NaturalIdRegion;
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy; import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
import org.hibernate.cache.spi.access.SoftLock;
/** /**
* @author Eric Dalquist * @author Eric Dalquist
@ -40,6 +42,63 @@ class ReadWriteNaturalIdRegionAccessStrategy extends AbstractReadWriteAccessStra
this.region = region; 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 @Override
Comparator getVersionComparator() { Comparator getVersionComparator() {
return region.getCacheDataDescription().getVersionComparator(); return region.getCacheDataDescription().getVersionComparator();