HHH-1775 - collection batch fetching

This commit is contained in:
Steve Ebersole 2012-10-26 12:56:08 -05:00
parent 7cecc68fb1
commit fb253b0297

View File

@ -25,21 +25,19 @@
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.jboss.logging.Logger;
import org.hibernate.EntityMode; import org.hibernate.EntityMode;
import org.hibernate.cache.spi.CacheKey; import org.hibernate.cache.spi.CacheKey;
import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.MarkerObject;
import org.hibernate.internal.util.collections.IdentityMap;
import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.jboss.logging.Logger;
/** /**
* Tracks entity and collection keys that are available for batch * Tracks entity and collection keys that are available for batch
@ -47,35 +45,35 @@
* can be re-used as a subquery for loading owned collections. * can be re-used as a subquery for loading owned collections.
* *
* @author Gavin King * @author Gavin King
* @author Steve Ebersole
* @author Guenther Demetz
*/ */
public class BatchFetchQueue { public class BatchFetchQueue {
private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, BatchFetchQueue.class.getName() ); private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, BatchFetchQueue.class.getName() );
/** private final PersistenceContext context;
* Defines a sequence of {@link EntityKey} elements that are currently
* elegible for batch-fetching.
* <p/>
* utilize a {@link LinkedHashMap} to maintain sequencing as well as uniqueness.
* <p/>
*/
private final Map <String,LinkedHashSet<EntityKey>> batchLoadableEntityKeys = new HashMap <String,LinkedHashSet<EntityKey>>(8);
/**
* cannot use PersistentCollection as keym because PersistentSet.hashCode() would force initialization immediately
*/
private final Map <CollectionPersister, LinkedHashMap <CollectionEntry, PersistentCollection>> batchLoadableCollections = new HashMap <CollectionPersister, LinkedHashMap <CollectionEntry, PersistentCollection>>(8);
/** /**
* A map of {@link SubselectFetch subselect-fetch descriptors} keyed by the * A map of {@link SubselectFetch subselect-fetch descriptors} keyed by the
* {@link EntityKey) against which the descriptor is registered. * {@link EntityKey) against which the descriptor is registered.
*/ */
private final Map subselectsByEntityKey = new HashMap(8); private final Map<EntityKey, SubselectFetch> subselectsByEntityKey = new HashMap<EntityKey, SubselectFetch>(8);
/** /**
* The owning persistence context. * Used to hold information about the entities that are currently eligible for batch-fetching. Ultimately
* used by {@link #getEntityBatch} to build entity load batches.
* <p/>
* A Map structure is used to segment the keys by entity type since loading can only be done for a particular entity
* type at a time.
*/ */
private final PersistenceContext context; private final Map <String,LinkedHashSet<EntityKey>> batchLoadableEntityKeys = new HashMap <String,LinkedHashSet<EntityKey>>(8);
/**
* Used to hold information about the collections that are currently eligible for batch-fetching. Ultimately
* used by {@link #getCollectionBatch} to build collection load batches.
*/
private final Map<String, LinkedHashMap<CollectionEntry, PersistentCollection>> batchLoadableCollections =
new HashMap<String, LinkedHashMap <CollectionEntry, PersistentCollection>>(8);
/** /**
* Constructs a queue for the given context. * Constructs a queue for the given context.
@ -95,6 +93,9 @@ public void clear() {
subselectsByEntityKey.clear(); subselectsByEntityKey.clear();
} }
// sub-select support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/** /**
* Retrieve the fetch descriptor associated with the given entity key. * Retrieve the fetch descriptor associated with the given entity key.
* *
@ -103,7 +104,7 @@ public void clear() {
* this entity key. * this entity key.
*/ */
public SubselectFetch getSubselect(EntityKey key) { public SubselectFetch getSubselect(EntityKey key) {
return (SubselectFetch) subselectsByEntityKey.get(key); return subselectsByEntityKey.get( key );
} }
/** /**
@ -113,7 +114,7 @@ public SubselectFetch getSubselect(EntityKey key) {
* @param subquery The fetch descriptor. * @param subquery The fetch descriptor.
*/ */
public void addSubselect(EntityKey key, SubselectFetch subquery) { public void addSubselect(EntityKey key, SubselectFetch subquery) {
subselectsByEntityKey.put(key, subquery); subselectsByEntityKey.put( key, subquery );
} }
/** /**
@ -123,7 +124,7 @@ public void addSubselect(EntityKey key, SubselectFetch subquery) {
* need to load its collections) * need to load its collections)
*/ */
public void removeSubselect(EntityKey key) { public void removeSubselect(EntityKey key) {
subselectsByEntityKey.remove(key); subselectsByEntityKey.remove( key );
} }
/** /**
@ -135,6 +136,9 @@ public void clearSubselects() {
subselectsByEntityKey.clear(); subselectsByEntityKey.clear();
} }
// entity batch support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/** /**
* If an EntityKey represents a batch loadable entity, add * If an EntityKey represents a batch loadable entity, add
* it to the queue. * it to the queue.
@ -170,94 +174,6 @@ public void removeBatchLoadableEntityKey(EntityKey key) {
} }
} }
} }
/**
* If an CollectionEntry represents a batch loadable collection, add
* it to the queue.
*/
public void addBatchLoadableCollection(PersistentCollection collection, CollectionEntry ce) {
LinkedHashMap<CollectionEntry, PersistentCollection> map = batchLoadableCollections.get( ce.getLoadedPersister());
if (map == null) {
map = new LinkedHashMap<CollectionEntry, PersistentCollection>(8);
batchLoadableCollections.put( ce.getLoadedPersister(), map);
}
map.put(ce, collection);
}
/**
* After a collection was initialized or evicted, we don't
* need to batch fetch it anymore, remove it from the queue
* if necessary
*/
public void removeBatchLoadableCollection(CollectionEntry ce) {
LinkedHashMap<CollectionEntry, PersistentCollection> map = batchLoadableCollections.get( ce.getLoadedPersister());
if (map != null) {
map.remove(ce);
}
}
/**
* Get a batch of uninitialized collection keys for a given role
*
* @param collectionPersister The persister for the collection role.
* @param id A key that must be included in the batch fetch
* @param batchSize the maximum number of keys to return
* @return an array of collection keys, of length batchSize (padded with nulls)
*/
public Serializable[] getCollectionBatch(
final CollectionPersister collectionPersister,
final Serializable id,
final int batchSize) {
Serializable[] keys = new Serializable[batchSize];
keys[0] = id;
int i = 1;
//int count = 0;
int end = -1;
boolean checkForEnd = false;
LinkedHashMap<CollectionEntry, PersistentCollection> map = batchLoadableCollections.get(collectionPersister);
if (map != null) {
for (Entry<CollectionEntry, PersistentCollection> me : map.entrySet()) {
CollectionEntry ce = me.getKey();
PersistentCollection collection = me.getValue();
if ( !collection.wasInitialized() ) { // should always be true
if ( checkForEnd && i == end ) {
return keys; //the first key found after the given key
}
//if ( end == -1 && count > batchSize*10 ) return keys; //try out ten batches, max
final boolean isEqual = collectionPersister.getKeyType().isEqual(
id,
ce.getLoadedKey(),
collectionPersister.getFactory()
);
if ( isEqual ) {
end = i;
//checkForEnd = false;
}
else if ( !isCached( ce.getLoadedKey(), collectionPersister ) ) {
keys[i++] = ce.getLoadedKey();
//count++;
}
if ( i == batchSize ) {
i = 1; //end of array, start filling again from start
if ( end != -1 ) {
checkForEnd = true;
}
}
}
else {
LOG.warn("Encountered initialized collection in BatchFetchQueue, this should not happen.");
}
}
}
return keys; //we ran out of keys to try
}
/** /**
* Get a batch of unloaded identifiers for this class, using a slightly * Get a batch of unloaded identifiers for this class, using a slightly
@ -280,9 +196,11 @@ public Serializable[] getEntityBatch(
int end = -1; int end = -1;
boolean checkForEnd = false; boolean checkForEnd = false;
LinkedHashSet<EntityKey> set = batchLoadableEntityKeys.get( persister.getEntityName() ); //TODO: this needn't exclude subclasses... // TODO: this needn't exclude subclasses...
if (set != null) {
for (EntityKey key : set) { LinkedHashSet<EntityKey> set = batchLoadableEntityKeys.get( persister.getEntityName() );
if ( set != null ) {
for ( EntityKey key : set ) {
if ( checkForEnd && i == end ) { if ( checkForEnd && i == end ) {
//the first id found after the given id //the first id found after the given id
return ids; return ids;
@ -296,8 +214,10 @@ public Serializable[] getEntityBatch(
} }
} }
if ( i == batchSize ) { if ( i == batchSize ) {
i = 1; //end of array, start filling again from start i = 1; // end of array, start filling again from start
if (end!=-1) checkForEnd = true; if ( end != -1 ) {
checkForEnd = true;
}
} }
} }
} }
@ -315,6 +235,98 @@ private boolean isCached(EntityKey entityKey, EntityPersister persister) {
} }
return false; return false;
} }
// collection batch support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* If an CollectionEntry represents a batch loadable collection, add
* it to the queue.
*/
public void addBatchLoadableCollection(PersistentCollection collection, CollectionEntry ce) {
final CollectionPersister persister = ce.getLoadedPersister();
LinkedHashMap<CollectionEntry, PersistentCollection> map = batchLoadableCollections.get( persister.getRole() );
if ( map == null ) {
map = new LinkedHashMap<CollectionEntry, PersistentCollection>( 16 );
batchLoadableCollections.put( persister.getRole(), map );
}
map.put( ce, collection );
}
/**
* After a collection was initialized or evicted, we don't
* need to batch fetch it anymore, remove it from the queue
* if necessary
*/
public void removeBatchLoadableCollection(CollectionEntry ce) {
LinkedHashMap<CollectionEntry, PersistentCollection> map = batchLoadableCollections.get( ce.getLoadedPersister().getRole() );
if ( map != null ) {
map.remove( ce );
}
}
/**
* Get a batch of uninitialized collection keys for a given role
*
* @param collectionPersister The persister for the collection role.
* @param id A key that must be included in the batch fetch
* @param batchSize the maximum number of keys to return
* @return an array of collection keys, of length batchSize (padded with nulls)
*/
public Serializable[] getCollectionBatch(
final CollectionPersister collectionPersister,
final Serializable id,
final int batchSize) {
Serializable[] keys = new Serializable[batchSize];
keys[0] = id;
int i = 1;
int end = -1;
boolean checkForEnd = false;
final LinkedHashMap<CollectionEntry, PersistentCollection> map = batchLoadableCollections.get( collectionPersister.getRole() );
if ( map != null ) {
for ( Entry<CollectionEntry, PersistentCollection> me : map.entrySet() ) {
final CollectionEntry ce = me.getKey();
final PersistentCollection collection = me.getValue();
if ( collection.wasInitialized() ) {
// should never happen
LOG.warn( "Encountered initialized collection in BatchFetchQueue, this should not happen." );
continue;
}
if ( checkForEnd && i == end ) {
return keys; //the first key found after the given key
}
final boolean isEqual = collectionPersister.getKeyType().isEqual(
id,
ce.getLoadedKey(),
collectionPersister.getFactory()
);
if ( isEqual ) {
end = i;
//checkForEnd = false;
}
else if ( !isCached( ce.getLoadedKey(), collectionPersister ) ) {
keys[i++] = ce.getLoadedKey();
//count++;
}
if ( i == batchSize ) {
i = 1; //end of array, start filling again from start
if ( end != -1 ) {
checkForEnd = true;
}
}
}
}
return keys; //we ran out of keys to try
}
private boolean isCached(Serializable collectionKey, CollectionPersister persister) { private boolean isCached(Serializable collectionKey, CollectionPersister persister) {
if ( persister.hasCache() ) { if ( persister.hasCache() ) {