From 33d399f1860618418d8bc3b0a92b9328ac2af077 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Fri, 20 Jan 2012 20:26:37 -0600 Subject: [PATCH] HHH-6974 - Add caching to new "load access" api for natural id loading --- .../internal/StatefulPersistenceContext.java | 154 +++++++++++++++--- .../engine/spi/PersistenceContext.java | 6 + .../DefaultResolveNaturalIdEventListener.java | 48 ++---- .../event/spi/ResolveNaturalIdEvent.java | 38 ++++- .../org/hibernate/internal/SessionImpl.java | 1 + .../entity/AbstractEntityPersister.java | 45 +---- .../persister/entity/EntityPersister.java | 2 +- .../jpa/naturalid/ImmutableNaturalIdTest.java | 2 + .../GoofyPersisterClassProvider.java | 2 +- .../test/legacy/CustomPersister.java | 2 +- 10 files changed, 201 insertions(+), 99 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index e635bff341..0aa9d1dc43 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -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> 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 pkToNaturalIdMap = new ConcurrentHashMap(); + private Map naturalIdToPkMap = new ConcurrentHashMap(); + } + + private Map naturalIdResolutionCacheMap + = new ConcurrentHashMap(); + + + @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 ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java index 7e401da627..a149015002 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java @@ -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); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultResolveNaturalIdEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultResolveNaturalIdEventListener.java index 04c4589134..822cceb504 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultResolveNaturalIdEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultResolveNaturalIdEventListener.java @@ -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; } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/ResolveNaturalIdEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/ResolveNaturalIdEvent.java index f9c3259626..7bfb910a9b 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/ResolveNaturalIdEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/ResolveNaturalIdEvent.java @@ -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 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 getNaturalIdValues() { return Collections.unmodifiableMap( naturalIdValues ); } + public Object[] getOrderedNaturalIdValues() { + return orderedNaturalIdValues; + } + public EntityPersister getEntityPersister() { return entityPersister; } - public String getEntityClassName() { + public String getEntityName() { return getEntityPersister().getEntityName(); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index b14a055c0e..632deefc5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -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 { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 1da5f10fae..217d57fef0 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -4505,28 +4505,9 @@ public abstract class AbstractEntityPersister @Override public Serializable loadEntityIdByNaturalId( - Map 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 { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index f05b10abb8..1ce9ccd127 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -324,7 +324,7 @@ public interface EntityPersister extends OptimisticCacheSource { /** * Load the id for the entity based on the natural id. */ - public Serializable loadEntityIdByNaturalId(Map naturalIdParameters, LockOptions lockOptions, + public Serializable loadEntityIdByNaturalId(Object[] naturalIdValues, LockOptions lockOptions, SessionImplementor session); /** diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/jpa/naturalid/ImmutableNaturalIdTest.java b/hibernate-core/src/matrix/java/org/hibernate/test/jpa/naturalid/ImmutableNaturalIdTest.java index 68b6a48470..055ce882a3 100644 --- a/hibernate-core/src/matrix/java/org/hibernate/test/jpa/naturalid/ImmutableNaturalIdTest.java +++ b/hibernate-core/src/matrix/java/org/hibernate/test/jpa/naturalid/ImmutableNaturalIdTest.java @@ -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(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/cfg/persister/GoofyPersisterClassProvider.java b/hibernate-core/src/test/java/org/hibernate/test/cfg/persister/GoofyPersisterClassProvider.java index 1c02fb18cb..1fcd763e56 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/cfg/persister/GoofyPersisterClassProvider.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cfg/persister/GoofyPersisterClassProvider.java @@ -240,7 +240,7 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver { } @Override - public Serializable loadEntityIdByNaturalId(Map naturalIdParameters, LockOptions lockOptions, + public Serializable loadEntityIdByNaturalId(Object[] naturalIdValues, LockOptions lockOptions, SessionImplementor session) { return null; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java index ff3254d1c0..413f17ba25 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java @@ -603,7 +603,7 @@ public class CustomPersister implements EntityPersister { } @Override - public Serializable loadEntityIdByNaturalId(Map naturalIdParameters, LockOptions lockOptions, + public Serializable loadEntityIdByNaturalId(Object[] naturalIdValues, LockOptions lockOptions, SessionImplementor session) { return null; }