HHH-6974 - Add caching to new "load access" api for natural id loading
This commit is contained in:
parent
622d9b37b9
commit
33d399f186
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue