/// <summary> /// Finish the process of collection-loading for this bound result set. Mainly this /// involves cleaning up resources and notifying the collections that loading is /// complete. /// </summary> /// <param name="persister">The persister for which to complete loading.</param> /// <param name="skipCache">Indicates if collection must not be put in cache.</param> /// <param name="cacheBatcher">The cache batcher used to batch put the collections into the cache.</param> /// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param> public async Task EndLoadingCollectionsAsync(ICollectionPersister persister, bool skipCache, CacheBatcher cacheBatcher, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (!loadContexts.HasLoadingCollectionEntries && (localLoadingCollectionKeys.Count == 0)) { return; } // in an effort to avoid concurrent-modification-exceptions (from // potential recursive calls back through here as a result of the // eventual call to PersistentCollection#endRead), we scan the // internal loadingCollections map for matches and store those matches // in a temp collection. the temp collection is then used to "drive" // the #endRead processing. List <CollectionKey> toRemove = new List <CollectionKey>(); List <LoadingCollectionEntry> matches = new List <LoadingCollectionEntry>(); foreach (CollectionKey collectionKey in localLoadingCollectionKeys) { ISessionImplementor session = LoadContext.PersistenceContext.Session; LoadingCollectionEntry lce = loadContexts.LocateLoadingCollectionEntry(collectionKey); if (lce == null) { log.Warn("In CollectionLoadContext#endLoadingCollections, localLoadingCollectionKeys contained [{0}], but no LoadingCollectionEntry was found in loadContexts", collectionKey); } else if (lce.ResultSet == resultSet && lce.Persister == persister) { matches.Add(lce); if (lce.Collection.Owner == null) { session.PersistenceContext.AddUnownedCollection(new CollectionKey(persister, lce.Key), lce.Collection); } if (log.IsDebugEnabled()) { log.Debug("removing collection load entry [{0}]", lce); } // todo : i'd much rather have this done from #endLoadingCollection(CollectionPersister,LoadingCollectionEntry)... loadContexts.UnregisterLoadingCollectionXRef(collectionKey); toRemove.Add(collectionKey); } } localLoadingCollectionKeys.ExceptWith(toRemove); await(EndLoadingCollectionsAsync(persister, matches, skipCache, cacheBatcher, cancellationToken)).ConfigureAwait(false); if ((localLoadingCollectionKeys.Count == 0)) { // todo : hack!!! // NOTE : here we cleanup the load context when we have no more local // LCE entries. This "works" for the time being because really // only the collection load contexts are implemented. Long term, // this cleanup should become part of the "close result set" // processing from the (sandbox/jdbc) jdbc-container code. loadContexts.Cleanup(resultSet); } }
/// <summary> /// Register a loading collection xref. /// </summary> /// <param name="entryKey">The xref collection key </param> /// <param name="entry">The corresponding loading collection entry </param> /// <remarks> /// This xref map is used because sometimes a collection is in process of /// being loaded from one result set, but needs to be accessed from the /// context of another "nested" result set processing. /// Implementation note: package protected, as this is meant solely for use /// by {@link CollectionLoadContext} to be able to locate collections /// being loaded by other {@link CollectionLoadContext}s/{@link ResultSet}s. /// </remarks> internal void RegisterLoadingCollectionXRef(CollectionKey entryKey, LoadingCollectionEntry entry) { if (xrefLoadingCollectionEntries == null) { xrefLoadingCollectionEntries = new Dictionary <CollectionKey, LoadingCollectionEntry>(); } xrefLoadingCollectionEntries[entryKey] = entry; }
private async Task EndLoadingCollectionAsync(LoadingCollectionEntry lce, ICollectionPersister persister, Action <CachePutData> cacheBatchingHandler, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (log.IsDebugEnabled()) { log.Debug("ending loading collection [{0}]", lce); } var persistenceContext = LoadContext.PersistenceContext; var session = persistenceContext.Session; bool statsEnabled = session.Factory.Statistics.IsStatisticsEnabled; var stopWath = new Stopwatch(); if (statsEnabled) { stopWath.Start(); } bool hasNoQueuedAdds = lce.Collection.EndRead(persister); // warning: can cause a recursive calls! (proxy initialization) if (persister.CollectionType.HasHolder()) { persistenceContext.AddCollectionHolder(lce.Collection); } CollectionEntry ce = persistenceContext.GetCollectionEntry(lce.Collection); if (ce == null) { ce = persistenceContext.AddInitializedCollection(persister, lce.Collection, lce.Key); } else { ce.PostInitialize(lce.Collection, persistenceContext); } bool addToCache = hasNoQueuedAdds && persister.HasCache && session.CacheMode.HasFlag(CacheMode.Put) && !ce.IsDoremove; // and this is not a forced initialization during flush if (addToCache) { await(AddCollectionToCacheAsync(lce, persister, cacheBatchingHandler, cancellationToken)).ConfigureAwait(false); } if (log.IsDebugEnabled()) { log.Debug("collection fully initialized: {0}", MessageHelper.CollectionInfoString(persister, lce.Collection, lce.Key, session)); } if (statsEnabled) { stopWath.Stop(); session.Factory.StatisticsImplementor.LoadCollection(persister.Role, stopWath.Elapsed); } }
private void EndLoadingCollection(LoadingCollectionEntry lce, ICollectionPersister persister) { if (log.IsDebugEnabled) { log.Debug("ending loading collection [" + lce + "]"); } ISessionImplementor session = LoadContext.PersistenceContext.Session; EntityMode em = session.EntityMode; bool statsEnabled = session.Factory.Statistics.IsStatisticsEnabled; var stopWath = new Stopwatch(); if (statsEnabled) { stopWath.Start(); } bool hasNoQueuedAdds = lce.Collection.EndRead(persister); // warning: can cause a recursive calls! (proxy initialization) if (persister.CollectionType.HasHolder(em)) { LoadContext.PersistenceContext.AddCollectionHolder(lce.Collection); } CollectionEntry ce = LoadContext.PersistenceContext.GetCollectionEntry(lce.Collection); if (ce == null) { ce = LoadContext.PersistenceContext.AddInitializedCollection(persister, lce.Collection, lce.Key); } else { ce.PostInitialize(lce.Collection); } bool addToCache = hasNoQueuedAdds && persister.HasCache && ((session.CacheMode & CacheMode.Put) == CacheMode.Put) && !ce.IsDoremove; // and this is not a forced initialization during flush if (addToCache) { AddCollectionToCache(lce, persister); } if (log.IsDebugEnabled) { log.Debug("collection fully initialized: " + MessageHelper.InfoString(persister, lce.Key, session.Factory)); } if (statsEnabled) { stopWath.Stop(); session.Factory.StatisticsImplementor.LoadCollection(persister.Role, stopWath.Elapsed); } }
/// <summary> Add the collection to the second-level cache </summary> /// <param name="lce">The entry representing the collection to add </param> /// <param name="persister">The persister </param> /// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param> private async Task AddCollectionToCacheAsync(LoadingCollectionEntry lce, ICollectionPersister persister, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ISessionImplementor session = LoadContext.PersistenceContext.Session; ISessionFactoryImplementor factory = session.Factory; if (log.IsDebugEnabled()) { log.Debug("Caching collection: {0}", MessageHelper.CollectionInfoString(persister, lce.Collection, lce.Key, session)); } if (!(session.EnabledFilters.Count == 0) && persister.IsAffectedByEnabledFilters(session)) { // some filters affecting the collection are enabled on the session, so do not do the put into the cache. log.Debug("Refusing to add to cache due to enabled filters"); // todo : add the notion of enabled filters to the CacheKey to differentiate filtered collections from non-filtered; // but CacheKey is currently used for both collections and entities; would ideally need to define two separate ones; // currently this works in conjunction with the check on // DefaultInitializeCollectionEventHandler.initializeCollectionFromCache() (which makes sure to not read from // cache with enabled filters). return; // EARLY EXIT!!!!! } IComparer versionComparator; object version; if (persister.IsVersioned) { versionComparator = persister.OwnerEntityPersister.VersionType.Comparator; object collectionOwner = LoadContext.PersistenceContext.GetCollectionOwner(lce.Key, persister); if (collectionOwner == null) { return; } version = LoadContext.PersistenceContext.GetEntry(collectionOwner).Version; } else { version = null; versionComparator = null; } CollectionCacheEntry entry = new CollectionCacheEntry(lce.Collection, persister); CacheKey cacheKey = session.GenerateCacheKey(lce.Key, persister.KeyType, persister.Role); bool put = await(persister.Cache.PutAsync(cacheKey, persister.CacheEntryStructure.Structure(entry), session.Timestamp, version, versionComparator, factory.Settings.IsMinimalPutsEnabled && session.CacheMode != CacheMode.Refresh, cancellationToken)).ConfigureAwait(false); if (put && factory.Statistics.IsStatisticsEnabled) { factory.StatisticsImplementor.SecondLevelCachePut(persister.Cache.RegionName); } }
/// <summary> /// Attempt to locate the loading collection given the owner's key. The lookup here /// occurs against all result-set contexts... /// </summary> /// <param name="persister">The collection persister </param> /// <param name="ownerKey">The owner key </param> /// <returns> The loading collection, or null if not found. </returns> public IPersistentCollection LocateLoadingCollection(ICollectionPersister persister, object ownerKey) { LoadingCollectionEntry lce = LocateLoadingCollectionEntry(new CollectionKey(persister, ownerKey)); if (lce != null) { if (log.IsDebugEnabled()) { log.Debug("returning loading collection:{0}", MessageHelper.CollectionInfoString(persister, ownerKey, Session.Factory)); } return(lce.Collection); } else { return(null); } }
/// <summary> /// Attempt to locate the loading collection given the owner's key. The lookup here /// occurs against all result-set contexts... /// </summary> /// <param name="persister">The collection persister </param> /// <param name="ownerKey">The owner key </param> /// <returns> The loading collection, or null if not found. </returns> public IPersistentCollection LocateLoadingCollection(ICollectionPersister persister, object ownerKey) { LoadingCollectionEntry lce = LocateLoadingCollectionEntry(new CollectionKey(persister, ownerKey, Session.EntityMode)); if (lce != null) { if (log.IsDebugEnabled) { log.Debug("returning loading collection:" + MessageHelper.InfoString(persister, ownerKey, Session.Factory)); } return(lce.Collection); } else { // todo : should really move this log statement to CollectionType, where this is used from... if (log.IsDebugEnabled) { log.Debug("creating collection wrapper:" + MessageHelper.InfoString(persister, ownerKey, Session.Factory)); } return(null); } }
/// <summary> Add the collection to the second-level cache </summary> /// <param name="lce">The entry representing the collection to add </param> /// <param name="persister">The persister </param> private void AddCollectionToCache(LoadingCollectionEntry lce, ICollectionPersister persister) { ISessionImplementor session = LoadContext.PersistenceContext.Session; ISessionFactoryImplementor factory = session.Factory; if (log.IsDebugEnabled) { log.Debug("Caching collection: " + MessageHelper.InfoString(persister, lce.Key, factory)); } if (!(session.EnabledFilters.Count == 0) && persister.IsAffectedByEnabledFilters(session)) { // some filters affecting the collection are enabled on the session, so do not do the put into the cache. log.Debug("Refusing to add to cache due to enabled filters"); // todo : add the notion of enabled filters to the CacheKey to differentiate filtered collections from non-filtered; // but CacheKey is currently used for both collections and entities; would ideally need to define two separate ones; // currently this works in conjunction with the check on // DefaultInitializeCollectionEventHandler.initializeCollectionFromCache() (which makes sure to not read from // cache with enabled filters). return; // EARLY EXIT!!!!! } IComparer versionComparator; object version; if (persister.IsVersioned) { versionComparator = persister.OwnerEntityPersister.VersionType.Comparator; object collectionOwner = LoadContext.PersistenceContext.GetCollectionOwner(lce.Key, persister); version = LoadContext.PersistenceContext.GetEntry(collectionOwner).Version; } else { version = null; versionComparator = null; } CollectionCacheEntry entry = new CollectionCacheEntry(lce.Collection, persister); CacheKey cacheKey = session.GenerateCacheKey(lce.Key, persister.KeyType, persister.Role); bool put = persister.Cache.Put(cacheKey, persister.CacheEntryStructure.Structure(entry), session.Timestamp, version, versionComparator, factory.Settings.IsMinimalPutsEnabled && session.CacheMode != CacheMode.Refresh); if (put && factory.Statistics.IsStatisticsEnabled) { factory.StatisticsImplementor.SecondLevelCachePut(persister.Cache.RegionName); } }
/// <summary> /// Retrieve the collection that is being loaded as part of processing this result set. /// </summary> /// <param name="persister">The persister for the collection being requested. </param> /// <param name="key">The key of the collection being requested. </param> /// <returns> The loading collection (see discussion above). </returns> /// <remarks> /// Basically, there are two valid return values from this method:<ul> /// <li>an instance of {@link PersistentCollection} which indicates to /// continue loading the result set row data into that returned collection /// instance; this may be either an instance already associated and in the /// midst of being loaded, or a newly instantiated instance as a matching /// associated collection was not found.</li> /// <li><i>null</i> indicates to ignore the corresponding result set row /// data relating to the requested collection; this indicates that either /// the collection was found to already be associated with the persistence /// context in a fully loaded state, or it was found in a loading state /// associated with another result set processing context.</li> /// </ul> /// </remarks> public IPersistentCollection GetLoadingCollection(ICollectionPersister persister, object key) { CollectionKey collectionKey = new CollectionKey(persister, key); if (log.IsDebugEnabled()) { log.Debug("starting attempt to find loading collection [{0}]", MessageHelper.InfoString(persister.Role, key)); } LoadingCollectionEntry loadingCollectionEntry = loadContexts.LocateLoadingCollectionEntry(collectionKey); if (loadingCollectionEntry == null) { // look for existing collection as part of the persistence context IPersistentCollection collection = loadContexts.PersistenceContext.GetCollection(collectionKey); if (collection != null) { if (collection.WasInitialized) { log.Debug("collection already initialized; ignoring"); return(null); // ignore this row of results! Note the early exit } else { // initialize this collection log.Debug("collection not yet initialized; initializing"); } } else { object owner = loadContexts.PersistenceContext.GetCollectionOwner(key, persister); bool newlySavedEntity = owner != null && loadContexts.PersistenceContext.GetEntry(owner).Status != Status.Loading; if (newlySavedEntity) { // important, to account for newly saved entities in query // todo : some kind of check for new status... log.Debug("owning entity already loaded; ignoring"); return(null); } else { // create one if (log.IsDebugEnabled()) { // Do not log the resultSet as-is, it is an IEnumerable which may get enumerated by loggers. // (Serilog does that.) See #1667. log.Debug("instantiating new collection [key={0}, rs={1}]", key, resultSet.GetType()); } collection = persister.CollectionType.Instantiate(loadContexts.PersistenceContext.Session, persister, key); } } collection.BeforeInitialize(persister, -1); collection.BeginRead(); localLoadingCollectionKeys.Add(collectionKey); loadContexts.RegisterLoadingCollectionXRef(collectionKey, new LoadingCollectionEntry(resultSet, persister, key, collection)); return(collection); } else { if (loadingCollectionEntry.ResultSet == resultSet) { log.Debug("found loading collection bound to current result set processing; reading row"); return(loadingCollectionEntry.Collection); } else { // ignore this row, the collection is in process of // being loaded somewhere further "up" the stack log.Debug("collection is already being initialized; ignoring row"); return(null); } } }
private void EndLoadingCollection( LoadingCollectionEntry lce, ICollectionPersister persister, Action <CachePutData> cacheBatchingHandler, bool skipCache) { if (log.IsDebugEnabled()) { log.Debug("ending loading collection [{0}]", lce); } var persistenceContext = LoadContext.PersistenceContext; var session = persistenceContext.Session; Stopwatch stopWatch = null; if (session.Factory.Statistics.IsStatisticsEnabled) { stopWatch = Stopwatch.StartNew(); } bool hasNoQueuedOperations = lce.Collection.EndRead(persister); // warning: can cause a recursive calls! (proxy initialization) if (persister.CollectionType.HasHolder()) { persistenceContext.AddCollectionHolder(lce.Collection); } CollectionEntry ce = persistenceContext.GetCollectionEntry(lce.Collection); if (ce == null) { ce = persistenceContext.AddInitializedCollection(persister, lce.Collection, lce.Key); } else { ce.PostInitialize(lce.Collection, persistenceContext); } bool addToCache = hasNoQueuedOperations && !skipCache && persister.HasCache && session.CacheMode.HasFlag(CacheMode.Put) && // and this is not a forced initialization during flush !ce.IsDoremove; if (addToCache) { AddCollectionToCache(lce, persister, cacheBatchingHandler); } if (!hasNoQueuedOperations) { lce.Collection.ApplyQueuedOperations(); } if (log.IsDebugEnabled()) { log.Debug("collection fully initialized: {0}", MessageHelper.CollectionInfoString(persister, lce.Collection, lce.Key, session)); } if (stopWatch != null) { stopWatch.Stop(); session.Factory.StatisticsImplementor.LoadCollection(persister.Role, stopWatch.Elapsed); } }
/// <summary> /// Register a loading collection xref. /// </summary> /// <param name="entryKey">The xref collection key </param> /// <param name="entry">The corresponding loading collection entry </param> /// <remarks> /// This xref map is used because sometimes a collection is in process of /// being loaded from one result set, but needs to be accessed from the /// context of another "nested" result set processing. /// Implementation note: package protected, as this is meant solely for use /// by {@link CollectionLoadContext} to be able to locate collections /// being loaded by other {@link CollectionLoadContext}s/{@link ResultSet}s. /// </remarks> internal void RegisterLoadingCollectionXRef(CollectionKey entryKey, LoadingCollectionEntry entry) { if (xrefLoadingCollectionEntries == null) xrefLoadingCollectionEntries = new Dictionary<CollectionKey, LoadingCollectionEntry>(); xrefLoadingCollectionEntries[entryKey] = entry; }