mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-02-09 12:44:49 +00:00
HHH-1775 - collection batch fetching
This commit is contained in:
parent
7cecc68fb1
commit
fb253b0297
@ -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() ) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user