This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.
Collections¶
To many relations are mapped by collections. We use a LinkedHashSet because we want a unique collection that does preserve the order (to be able to define a default sorting).
In order to provide the same behaviour for collections as in the old API, some extensions were necessary.
Collection filtering¶
All collections will be lazily initialized, this happens when a to many relation is resolved. However, we don’t just want to return all rows in the database. The elements in the collection should be filtered depending on, for example, the current business unit or security.
Hibernate Filters are unfortunately not flexible enough for our needs. Therefore a custom InitializeCollectionEventListener was implemented: ExtendedInitializeCollectionEventListener.
Unfortunately it is not possible to simply provide a list of entities to initialize a collection, because they
are initialized directly from a ResultSet (see PersistentCollection#readFrom()
).
This is done when a Loader contains a collection persister.
We did not find an appropriate way to implement such a loader to load collections with dynamically generated conditions,
therefore a workaround was necessary in ExtendedInitializeCollectionEventListener.
We use our CollectionInitializer to load the elements.
Afterwars the collection.injectLoadedState(...)
is used to pass the value to Hibernate.
Warning
An empty collection does not mean that the corresponding database table is empty as well (all rows might be filtered). Hibernate tries to use some shortcuts if a collection is empty (for example clearing a many to many mapping table). To avoid this, we need to indicate to Hibernate, that the collection might not be empty:
ExtendedBasicCollectionPersister#isAffectedByEnabledFilters
ReloadablePersistentSet#isSnapshotEmpty
CollectionInitializer¶
The loading of the collection elements is delegated to an instance of CollectionInitializer.
The ExtendedInitializeCollectionEventListener fetches the matching CollectionInitializer from the CollectionInitializationService.
A CollectionInitializer implementation
can be enabled for a specific association using the supports()
method. The priority()
method should be overridden
by more specific implementations so that they are selected before the more generic implementations.
The CollectionInitializer provides two main methods:
getCollectionElements()
fetches all elements of a given association, including support for pagination and ordering
countCollectionElements()
counts the collection elements without loading them all
The default CollectionInitializer is the DefaultCollectionInitializer.
Most of the functionality is implemented in AbstractCollectionInitializer, which is the base class of all implementations. It uses the CriteriaQueryBuilder to execute a query for the reverse relation. Since the query is dynamically modified by all QueryBuilderInterceptor, security and business unit conditions are added as well (which would not be the case when using Hibernate collections directly).
See Query Builder for more information about this topic.
Currently there are a couple of special implementations of CollectionInitializer:
AbstractEntityDocsCollectionInitializer is the base class for several entity-docs related collection initializers. There are no ACL rules for entity-docs, therefore a special implementation is required for loading entity-docs collections.
NodeChildrenCollectionInitializer is a collection initializer that improves the performance of loading child nodes of
Folder
entities.
Collection reloading¶
Per default a collection cannot be reloaded from the database once it has been initialized. However, when a relation is resolved the collection should always be reloaded from the database because a relation may be resolved multiple times within the same transaction with different privileges.
To support this, we use a custom persistent collection type, the ReloadablePersistentCollectionType. This type is configured for all collections (see Entity class generation).
The concrete collection implementation is the ReloadablePersistentSet, which has the following features:
Reloading¶
See ReloadablePersistentSet#reloadCollection
.
A collection can only be reloaded if it is already initialized and not transient.
If a collection is reloaded, all uncommitted changes will be lost, therefore we need to track them
so that they can be applied again after the reload.
These tracked changes must be reset after the session is flushed, this is done by overriding
PersistentCollection#postAction()
.
Warning
There is one case which is not supported: If an element is removed and the collection does no longer contain the removed element after the reload, an exception will be thrown, as the remove operation would be lost.
The code snippets which unload and then load the collection have been taken from different classes of the Hibernate source code.
The initialized flag of the collection needs to be reset to false (using reflection)
The collection needs to be evicted from the session (based on code from EvictVisitor)
The collection needs to be loaded from the database and attached to the session again
Uncommitted changes must be applied again
Delayed operation¶
Hibernate supports delayed (queued) operations, that get executed only after the collection was initialized.
This enables adding and removing elements without initializing the collection.
Queued operations cannot be used on the owning side of a many to many association because the owning side is
responsible for persisting the association. This means that the element has to be normally added to the collection
so that the change will be detected.
We use the delayed operations wherever possible (that means if an element is added to or removed from an uninitialized,
inverse collection) for performance reasons.
The queued operations are executed during PersistentCollection#afterInitialize()
. As our collection loading process
is different than normal, we call this manually from ReloadablePersistentSet#endRead()
.
Note
An alternative to the implemented approach would be to use the standard collection handling and just run a query whenever a relation is resolved. It would then be required to synchronize the changes to the owning side of the association (this would cause an unnecessary collection load for many to many relations, unless the mapping table is mapped to an entity (which might make sense performance wise anyway)). This might a viable option in case the current approach fails with future hibernate versions.