HHH-6974 - Add caching to new "load access" api for natural id loading

This commit is contained in:
Steve Ebersole 2012-01-20 20:26:37 -06:00
parent 622d9b37b9
commit 33d399f186
10 changed files with 201 additions and 99 deletions

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;
@ -36,6 +37,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.collections.map.AbstractReferenceMap;
import org.apache.commons.collections.map.ReferenceMap;
@ -315,34 +317,34 @@ public class StatefulPersistenceContext implements PersistenceContext {
return null;
}
// if the natural-id is marked as non-mutable, it is not retrieved during a
// normal database-snapshot operation...
int[] props = persister.getNaturalIdentifierProperties();
boolean[] updateable = persister.getPropertyUpdateability();
boolean allNatualIdPropsAreUpdateable = true;
for ( int prop : props ) {
if ( !updateable[prop] ) {
allNatualIdPropsAreUpdateable = false;
break;
}
// let's first see if it is part of the natural id cache...
final Object[] cachedValue = findCachedNaturalId( persister, id );
if ( cachedValue != null ) {
return cachedValue;
}
if ( allNatualIdPropsAreUpdateable ) {
// do this when all the properties are updateable since there is
// a certain likelihood that the information will already be
// check to see if the natural id is mutable/immutable
if ( persister.getEntityMetamodel().hasImmutableNaturalId() ) {
// an immutable natural-id is not retrieved during a normal database-snapshot operation...
final Object[] dbValue = persister.getNaturalIdentifierSnapshot( id, session );
cacheNaturalIdResolution( persister, id, dbValue );
return dbValue;
}
else {
// for a mutable natural there is a likelihood that the the information will already be
// snapshot-cached.
Object[] entitySnapshot = getDatabaseSnapshot( id, persister );
final int[] props = persister.getNaturalIdentifierProperties();
final Object[] entitySnapshot = getDatabaseSnapshot( id, persister );
if ( entitySnapshot == NO_ROW ) {
return null;
}
Object[] naturalIdSnapshot = new Object[ props.length ];
final Object[] naturalIdSnapshotSubSet = new Object[ props.length ];
for ( int i = 0; i < props.length; i++ ) {
naturalIdSnapshot[i] = entitySnapshot[ props[i] ];
naturalIdSnapshotSubSet[i] = entitySnapshot[ props[i] ];
}
return naturalIdSnapshot;
}
else {
return persister.getNaturalIdentifierSnapshot( id, session );
cacheNaturalIdResolution( persister, id, naturalIdSnapshotSubSet );
return naturalIdSnapshotSubSet;
}
}
@ -1652,6 +1654,8 @@ public class StatefulPersistenceContext implements PersistenceContext {
}
// INSERTED KEYS HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private HashMap<String,List<Serializable>> insertedKeysMap;
@Override
@ -1691,7 +1695,117 @@ public class StatefulPersistenceContext implements PersistenceContext {
}
}
// NATURAL ID RESOLUTION HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public void loadedStateUpdatedNotification(EntityEntry entityEntry) {
}
private class NaturalId {
private final EntityPersister persister;
private final Object[] values;
private NaturalId(EntityPersister persister, Object[] values) {
this.persister = persister;
this.values = values;
}
public EntityPersister getPersister() {
return persister;
}
public Object[] getValues() {
return values;
}
@Override
public boolean equals(Object other) {
if ( this == other ) {
return true;
}
if ( other == null || getClass() != other.getClass() ) {
return false;
}
final NaturalId that = (NaturalId) other;
return persister.equals( that.persister )
&& Arrays.equals( values, that.values );
}
@Override
public int hashCode() {
int result = persister.hashCode();
result = 31 * result + Arrays.hashCode( values );
return result;
}
}
private class NaturalIdResolutionCache implements Serializable {
private final EntityPersister persister;
private NaturalIdResolutionCache(EntityPersister persister) {
this.persister = persister;
}
private Map<Serializable,NaturalId> pkToNaturalIdMap = new ConcurrentHashMap<Serializable, NaturalId>();
private Map<NaturalId,Serializable> naturalIdToPkMap = new ConcurrentHashMap<NaturalId,Serializable>();
}
private Map<EntityPersister,NaturalIdResolutionCache> naturalIdResolutionCacheMap
= new ConcurrentHashMap<EntityPersister, NaturalIdResolutionCache>();
@Override
public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk) {
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
if ( entityNaturalIdResolutionCache == null ) {
return null;
}
else {
final NaturalId naturalId = entityNaturalIdResolutionCache.pkToNaturalIdMap.get( pk );
return naturalId == null ? null : naturalId.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." );
}
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
if ( entityNaturalIdResolutionCache == null ) {
return null;
}
else {
final NaturalId naturalId = new NaturalId( persister, naturalIdValues );
return entityNaturalIdResolutionCache.naturalIdToPkMap.get( naturalId );
}
}
@Override
public void cacheNaturalIdResolution(EntityPersister persister, 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 NaturalId naturalId = new NaturalId( persister, naturalIdValues );
entityNaturalIdResolutionCache.pkToNaturalIdMap.put( pk, naturalId );
entityNaturalIdResolutionCache.naturalIdToPkMap.put( naturalId, pk );
}
}

View File

@ -684,4 +684,10 @@ public interface PersistenceContext {
public boolean wasInsertedDuringTransaction(EntityPersister persister, Serializable id);
public void loadedStateUpdatedNotification(EntityEntry entityEntry);
public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk);
public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalId);
public void cacheNaturalIdResolution(EntityPersister persister, Serializable pk, Object[] naturalId);
}

View File

@ -39,6 +39,7 @@ import org.hibernate.pretty.MessageHelper;
* in response to generated load events.
*
* @author Eric Dalquist
* @author Steve Ebersole
*/
public class DefaultResolveNaturalIdEventListener
extends AbstractLockUpgradeEventListener
@ -81,16 +82,6 @@ public class DefaultResolveNaturalIdEventListener
}
Serializable entityId = resolveFromSessionCache( event );
if ( entityId == REMOVED_ENTITY_MARKER ) {
LOG.debug( "Load request found matching entity in context, but it is scheduled for removal; returning null" );
return null;
}
if ( entityId == INCONSISTENT_RTN_CLASS_MARKER ) {
LOG.debug(
"Load request found matching entity in context, but the matched entity was of an inconsistent return type; returning null"
);
return null;
}
if ( entityId != null ) {
if ( LOG.isTraceEnabled() ) {
LOG.trace(
@ -136,29 +127,10 @@ public class DefaultResolveNaturalIdEventListener
* @return The entity from the session-level cache, or null.
*/
protected Serializable resolveFromSessionCache(final ResolveNaturalIdEvent event) {
// SessionImplementor session = event.getSession();
// Object old = session.getEntityUsingInterceptor( keyToLoad );
//
// if ( old != null ) {
// // this object was already loaded
// EntityEntry oldEntry = session.getPersistenceContext().getEntry( old );
// if ( options.isCheckDeleted() ) {
// Status status = oldEntry.getStatus();
// if ( status == Status.DELETED || status == Status.GONE ) {
// return REMOVED_ENTITY_MARKER;
// }
// }
// if ( options.isAllowNulls() ) {
// final EntityPersister persister = event.getSession().getFactory().getEntityPersister(
// keyToLoad.getEntityName() );
// if ( ! persister.isInstance( old ) ) {
// return INCONSISTENT_RTN_CLASS_MARKER;
// }
// }
// upgradeLock( old, oldEntry, event.getLockOptions(), event.getSession() );
// }
return null;
return event.getSession().getPersistenceContext().findCachedNaturalIdResolution(
event.getEntityPersister(),
event.getOrderedNaturalIdValues()
);
}
/**
@ -223,10 +195,16 @@ public class DefaultResolveNaturalIdEventListener
* @return The object loaded from the datasource, or null if not found.
*/
protected Serializable loadFromDatasource(final ResolveNaturalIdEvent event) {
return event.getEntityPersister().loadEntityIdByNaturalId(
event.getNaturalIdValues(),
final Serializable pk = event.getEntityPersister().loadEntityIdByNaturalId(
event.getOrderedNaturalIdValues(),
event.getLockOptions(),
event.getSession()
);
event.getSession().getPersistenceContext().cacheNaturalIdResolution(
event.getEntityPersister(),
pk,
event.getOrderedNaturalIdValues()
);
return pk;
}
}

View File

@ -27,6 +27,7 @@ import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.persister.entity.EntityPersister;
@ -42,6 +43,7 @@ public class ResolveNaturalIdEvent extends AbstractEvent {
private final EntityPersister entityPersister;
private final Map<String, Object> naturalIdValues;
private final Object[] orderedNaturalIdValues;
private final LockOptions lockOptions;
private Serializable entityId;
@ -61,8 +63,23 @@ public class ResolveNaturalIdEvent extends AbstractEvent {
throw new IllegalArgumentException( "EntityPersister is required for loading" );
}
if ( ! entityPersister.hasNaturalIdentifier() ) {
throw new HibernateException( "Entity did not define a natural-id" );
}
if ( naturalIdValues == null || naturalIdValues.isEmpty() ) {
throw new IllegalArgumentException( "id to load is required for loading" );
throw new IllegalArgumentException( "natural-id to load is required" );
}
if ( entityPersister.getNaturalIdentifierProperties().length != naturalIdValues.size() ) {
throw new HibernateException(
String.format(
"Entity [%s] defines its natural-id with %d properties but only %d were specified",
entityPersister.getEntityName(),
entityPersister.getNaturalIdentifierProperties().length,
naturalIdValues.size()
)
);
}
if ( lockOptions.getLockMode() == LockMode.WRITE ) {
@ -75,17 +92,34 @@ public class ResolveNaturalIdEvent extends AbstractEvent {
this.entityPersister = entityPersister;
this.naturalIdValues = naturalIdValues;
this.lockOptions = lockOptions;
int[] naturalIdPropertyPositions = entityPersister.getNaturalIdentifierProperties();
orderedNaturalIdValues = new Object[naturalIdPropertyPositions.length];
int i = 0;
for ( int position : naturalIdPropertyPositions ) {
final String propertyName = entityPersister.getPropertyNames()[position];
if ( ! naturalIdValues.containsKey( propertyName ) ) {
throw new HibernateException(
String.format( "No value specified for natural-id property %s#%s", getEntityName(), propertyName )
);
}
orderedNaturalIdValues[i++] = naturalIdValues.get( entityPersister.getPropertyNames()[position] );
}
}
public Map<String, Object> getNaturalIdValues() {
return Collections.unmodifiableMap( naturalIdValues );
}
public Object[] getOrderedNaturalIdValues() {
return orderedNaturalIdValues;
}
public EntityPersister getEntityPersister() {
return entityPersister;
}
public String getEntityClassName() {
public String getEntityName() {
return getEntityPersister().getEntityName();
}

View File

@ -161,6 +161,7 @@ import org.jboss.logging.Logger;
* This class is not thread-safe.
*
* @author Gavin King
* @author Steve Ebersole
*/
public final class SessionImpl extends AbstractSessionImpl implements EventSource {

View File

@ -4505,28 +4505,9 @@ public abstract class AbstractEntityPersister
@Override
public Serializable loadEntityIdByNaturalId(
Map<String, ?> naturalIdValues,
Object[] naturalIdValues,
LockOptions lockOptions,
SessionImplementor session) {
if ( ! entityMetamodel.hasNaturalIdentifier() ) {
throw new HibernateException(
String.format( "Entity [%s] does not define a natural-id", getEntityName() )
);
}
final int[] naturalIdPropertyIndexes = this.getNaturalIdentifierProperties();
if ( naturalIdPropertyIndexes.length != naturalIdValues.size() ) {
throw new HibernateException(
String.format(
"Entity [%s] defines its natural-id with %d properties but only %d were specified",
getEntityName(),
naturalIdPropertyIndexes.length,
naturalIdValues.size()
)
);
}
if ( LOG.isTraceEnabled() ) {
LOG.tracef(
"Resolving natural-id [%s] to id : %s ",
@ -4542,25 +4523,11 @@ public abstract class AbstractEntityPersister
.prepareStatement( sqlEntityIdByNaturalIdString );
try {
int positions = 1;
for ( int naturalIdIdx : naturalIdPropertyIndexes ) {
final StandardProperty property = entityMetamodel.getProperties()[naturalIdIdx];
if ( ! naturalIdValues.containsKey( property.getName() ) ) {
throw new HibernateException(
String.format(
"No value specified for natural-id property %s#%s",
getEntityName(),
property.getName()
)
);
}
final Object value = naturalIdValues.get( property.getName() );
if ( value == null ) {
}
final Type propertyType = property.getType();
propertyType.nullSafeSet( ps, value, positions, session );
positions += propertyType.getColumnSpan( session.getFactory() );
int loop = 0;
for ( int idPosition : getNaturalIdentifierProperties() ) {
final Type type = getPropertyTypes()[idPosition];
type.nullSafeSet( ps, naturalIdValues[loop++], positions, session );
positions += type.getColumnSpan( session.getFactory() );
}
ResultSet rs = ps.executeQuery();
try {

View File

@ -324,7 +324,7 @@ public interface EntityPersister extends OptimisticCacheSource {
/**
* Load the id for the entity based on the natural id.
*/
public Serializable loadEntityIdByNaturalId(Map<String, ?> naturalIdParameters, LockOptions lockOptions,
public Serializable loadEntityIdByNaturalId(Object[] naturalIdValues, LockOptions lockOptions,
SessionImplementor session);
/**

View File

@ -172,10 +172,12 @@ public class ImmutableNaturalIdTest extends AbstractJPATest {
s.beginTransaction();
u = (User) s.byNaturalId( User.class ).using( "userName", "steve" ).load();
assertNotNull( u );
assertEquals( 1, sessionFactory().getStatistics().getEntityLoadCount() );
assertEquals( sessionFactory().getStatistics().getQueryExecutionCount(), 0 );
assertEquals( sessionFactory().getStatistics().getQueryCacheHitCount(), 0 );
u = (User) s.byNaturalId( User.class ).using( "userName", "steve" ).load();
assertNotNull( u );
assertEquals( 1, sessionFactory().getStatistics().getEntityLoadCount() );
assertEquals( sessionFactory().getStatistics().getQueryExecutionCount(), 0 );
assertEquals( sessionFactory().getStatistics().getQueryCacheHitCount(), 0 );
s.getTransaction().commit();

View File

@ -240,7 +240,7 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
}
@Override
public Serializable loadEntityIdByNaturalId(Map<String, ?> naturalIdParameters, LockOptions lockOptions,
public Serializable loadEntityIdByNaturalId(Object[] naturalIdValues, LockOptions lockOptions,
SessionImplementor session) {
return null;
}

View File

@ -603,7 +603,7 @@ public class CustomPersister implements EntityPersister {
}
@Override
public Serializable loadEntityIdByNaturalId(Map<String, ?> naturalIdParameters, LockOptions lockOptions,
public Serializable loadEntityIdByNaturalId(Object[] naturalIdValues, LockOptions lockOptions,
SessionImplementor session) {
return null;
}