This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.
Abstract entity base class¶
An instance of PrimaryKey is created when
is called for the first name. The key is cached so that always the same instance is returned, which is expected by
some of the business code.
The PropertyAccessorServiceImpl efficiently reads and writes entity properties.
Different strategies are used depending on the property type. For all persistent properties, the calls are delegated to Hibernate’s EntityPersister.
Transient properties are manually accessed using reflection.
It is important that all accessors are cached for performance reasons.
All calls to the different
Entity#getValue() methods are delegated to
where the actual field is resolved and read using the PropertyAccessorService.
For backwards compatibility, the resulting value is passed to
TypeManager#isolate() before it is returned
(which creates a copy of Binary instances).
It is also attempted to convert the value to the requested type.
All calls to the different
Entity#setValue() methods are delegated to
where the actual field is resolved and updated using the PropertyAccessorService.
At first the value is converted to the required target type (if this is not already the case and a suitable Converter exists).
The resulting value is then compared to the old value - if they are the same, the method silently returns.
After the value has been set, a
EntityFacadeListener#entityChanging() event will be fired.
internalSetValue() bypasses the
entity interceptors (security, localization etc). Therefore passing the field name to
normally required before calling these methods. It may be omitted for certain internal calls where the interceptors
are not required.
When Hibernate internally reads or writes properties of an entity, the field is accessed directly and no additional code is executed.
If no transaction is running when
setValue() is called (or a relation is changed) an exception will be thrown,
because otherwise these changes would be silently lost.
An association in hibernate is simply an instance of the referenced type (or a collection if it’s a to-many relation).
In the old API it was required to ‘resolve’ a relation (
Entity#resolve() ) to a RelationQuery.
This relation query can then be executed to get an instance of Relation.
All to one associations are explicitly configured to be loaded lazily (JPA default is eager).
All access (read or write) goes through the RelationInterceptor,
this allows other modules to add functionality (for example security checks).
In order to enforce cleaner code, methods that were meant for to-many associations (for example
are not supported.
The ToOneRelationAdapter provides the last interceptor in the chain, which actually accesses the underlying entity.
Reading the value means simply calling
Entity#getValue()on the source entity. Internally this calls the generated getter for the association.
When a value is written some more actions are performed (if the new value is the same as the current one, the call is silently ignored):
The updated value is set to the source entity using
As all associations in nice2 are bi-directional, the inverse association (in this case always a one-to-many association) needs to be updated. The previous value (if present) needs to be removed, and the new value (if not null) needs to be added from/to the inverse association.
EntityFacadeListener#entityRelationChanging()event is fired.
If the previous value was not null, an event is fired for removing the old value (the
adjustingflag is true if the new value is not null)
If the new value is not null, an event is fired for adding the new value.
Collections are loaded lazily by default. We use a special implementation of the PersistentSet that supports reloading a collection from the database.
See Collection reloading for further information.
Every time a to-many relation is resolved, it should be reloaded from the database (because this is the behaviour of the old persistence implementation).
ToManyRelationQueryAdapter is the implementation of RelationQuery used for to-many associations. It mainly delegates to the wrapped collection of entities. However hibernate does not support pagination or (dynamic) sorting of associations, therefore these cases have to be implemented specifically: If a relations needs to be resolved with a specific ordering or pagination an additional query will be executed to get the desired results (the collection won’t be touched). The results are returned as an unmodifiable collection, because changes to this collection would be ignored (as it is unknown to hibernate).
Like its ‘to-one’ counterpart it implements the final RelationInterceptor that actually accesses the underlying collection and also enforces the usage of the correct methods.
If an operation (
removeEntity) causes a change:
The underlying collection is modified
The inverse association (in this case one-to-many or a many-to-many) is adjusted
EntityFacadeListener#entityRelationChanging()event is fired.
An event is fired if an entity has been added or removed (the
adjustingflag is always false as there is only one event)
If all values are replaced using
setEntities(), first an event is fired for all removed entities. After that an event is fired for all newly added entities. If an entity is part of the collection before and after the operation, no add or remove event should be fired for this entity. The
adjustingis always false, except for the very last event.
size() does not initialize the collection, but executes a
COUNT query. This is important if the collection is
large. However this means that
size() should not be called when the collection is going to be initialized anyway
(for example when
toList() is called), because that would lead to an unnecessary query.
Syncing inverse associations¶
When the user changes an association, the other side should be updated automatically by the framework, as all associations are bi-directional at the moment.
When doing this, care must be taken not to unnecessarily initialize lazy collections, as this would have a negative performance impact. On the other hand, sometimes this is necessary in many to many associations, when the user did not update the owning side (see Entity class generation).
If the reverse side is a many-to-one association it can just be updated without any performance penalty (it is also necessary to do so because the many-to-one side is always the owning side).
If the reverse side is a one-to-many or inverse many-to-many association, the collection may be updated but the addition may be queued if the collection is not initialized yet.
If the reverse side is the owning side of many-to-many association, the collection must always be updated (and perhaps initialized). Otherwise the changes would not be persisted by hibernate.
See Delayed operation for further information about queued operations.
In the future it might be worth to check if we want to explicitly map the mapping table with an entity class. This would allow using many-to-one/one-to-many associations and avoid unnecessary collection initialization.
The states are checked in the following order (important):
The phantom state is tracked by the
wasDeletedfield. This is necessary because of two reasons. First, the actual delete query is not immediately fired (but just before the transaction is committed, to make sure that all delete statements are executed in the correct order), but the state has to be PHANTOM immediately after the
delete()method was called. Second, after the session is flushed, the deleted entity is no longer in Hibernate’s persistence context, so it would not be possible to tell if an entity is deleted using
If an entity has a primary key which is auto-generated by the database and this key is null, the state of the entity must be conception. For primary keys which are generated by the user (for example strings) this does not work, instead it is checked whether an EntityEntry for this entity exists.
Additionally if an entity has its primary key already set and its EntityEntry status is
SAVINGthe entity is also in conception state. This can happen when
Entity#getState()is called from inside a validator (see
If there is no EntityEntry for an entity and it is not in conception state or deleted, it must be invalid.
See Dirty checking
If all other states do not apply, the entity must be clean (that means persisted and unchanged).
The Entity interface differentiates between
properties. A field is touched when
setValue() has been called at least once for that field, even if the value is still the same.
As this distinction rarely makes sense, we no longer support it - only
changed fields are returned from the
dirty checking methods (for example
The dirty fields are managed by the abstract base class AbstractDirtyCheckingEntity
All calls to the setter methods are intercepted using a custom PropertyAccessorService.
If the value to be set is different from the Old value, the field is marked as changed.
To check for modified collections (to-many relations) we can simply use the
isDirty() method of the
The list of changed fields needs to be reset when the changes are flushed to the database. This is done by the ValidationInterceptor after the entity validation has been completed.
Instead of manually keeping track of all the changes it would be possible to just always compare the current value
with the old value, when we need the changed fields. However this is a bit of a performance problem, because the
changed fields are needed quite often, especially by
Entity#getState() to check if the current state is
The Entity interface allows to query for the old value. This is the value of a certain property when it was loaded from the database at the beginning of the transaction, ignoring all uncommitted changes.
This is achieved by checking the ‘loaded state’ of the EntityEntry, which can be retrieved from the PersistenceContext. This is where hibernate stores the state of the entity when it is loaded and this state is also used for hibernate’s default dirty checking mechanism.
The EntityInterceptor interface allows customizing the core entity functionality. The following functions can be intercepted:
Reading and writing fields
An entity interceptor instance is injected into every entity by the EntityFactoryImpl.
The instance is created by the EntityInterceptorFactoryImpl
which combines all interceptor contributions into an interceptor chain.
The inner most interceptor (which actually accesses the entity fields and so on) is provided by the entity itself
The inner interceptor is wrapped in a LazyInterceptor
to avoid recursive proxy initialization (
proxy initialization ->
proxy initialization …).
EntityInterceptor#accessField() can be used to intercept read or write access to a field.
It is always called when a value is accessed by the entity (typically when
Entity#get/setValue() is called).
The default inner interceptor simply resolves the field name using the FieldResolver. If write access is requested it additionally checks if the field is not a primary key or other generated field.
uses this method to check the read or write permission of the given field. If the given field is a localized field, the base field (
label_de) is used to check permissions.
EntityInterceptor#deleteEntity() is called when an entity is deleted (
The inner interceptor fires an
EntityFacadeListener#entityDeleting() event and (unless the entity is unsaved)
schedules the entity for deletion with the EntityTransactionContext.
In addition the SecurityEntityInterceptorContribution
checks if the
delete permission is granted for the current user.
The inner interceptors are provided by the AbstractRelationAdapter
implementations. These update the relation value or collection and fire an
In addition the SecurityEntityInterceptorContribution checks if the current user is allowed to modify a relation.
checks if the business unit of an entity may be manually changed by the user (only business unit types
NONE may be changed by the user).
The FieldResolverImpl resolves a property name to the name of the corresponding entity field. Usually the property name is equal to the entity field name, however there are two exceptions:
Localized fields: if the base field of a localized field is requested (e.g.
label) it is resolved to the field of the current locale (e.g.
When java reserved words are used as a field name in the entity model, the field name needs to be adjusted (see
It is called whenever a field is accessed or referenced by name, for example when reading or writing fields or when compiling queries.