This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.

Transaction context

The EntityTransactionContext collects entities that have been created or deleted during the transaction and applies these changes to the session before the transaction is committed.

All new entity instances are tracked automatically and the user of the API does not have to call Session#persist() (or equivalent) manually to add an entity to the persistence context.

All calls to Entity#delete() are recorded and then executed before the transaction is committed. The calls will be reordered so that no constraint violations can occur on the database. That means it does not matter in which order the entities are deleted by the user. To avoid a datebase timeout the entities are deleted in partitions.

The service is a singleton and all changes are stored in maps using the current Session as key. The values are removed by the TransactionControl after each commit or rollback. To prevent memory leaks the entire operation is wrapped in a try/finally block that clears the values of the current session after the operation has been completed.

Created entities

In a first step all entities that are created and deleted in the same transaction are removed from the list of entities to be inserted. This is necessary sometimes, as all entities are going to be saved automatically when they are created within a transaction.

After that all newly created entities are passed to the EntityInsertActionResolver which creates a list of EntityInsertAction. The returned list is ordered such that no constraint violations can occur when the insert statements are executed.

All that needs to be done is to make sure that unsaved entities which have non-nullable many-to-one reference to another unsaved entity are saved after the referenced entity. All other cases (many-to-many associations or nullable references) are correctly ordered by hibernate.

Note

Also have a look at the javadoc in EntityTransactionContextTest and EntityInsertActionResolver for more implementation details.

Entity factory

All Entity instances are instantiated by the EntityFactoryImpl:

  • All entities created by the user are delegated from PersistenceService#create() to the entity factory.

  • Entities that are instantiated by Hibernate (for example as a result of a query) are also delegated to the entity factory by the CustomEntityPersister (see Entity instantiation).

The first purpose is to inject several necessary services into the Entity instances, for example the current Context or the EntityModel.

In addition the following listeners are invoked when an entity was instantiated:

  • EntityFacadeListener#entityCreating is called for every newly created entity (this is not called for entities that are loaded from the database).

  • EntityCreationListener are called for every created entity instance (entityCreated() for new entities and entityLoaded() for existing entities).

Newly created entities are added to EntityTransactionContext so that they will be persisted at the end of the transaction.

Note

There is a caveat for entities loaded from the database: It is possible that the entity instantiation is actually the initialization of a HibernateProxy. In this case it is important to pass the proxy instance to the listeners (instead of the actual entity instance). Otherwise there are multiple entity instances representing the same database row, which will lead to unexpected side effects.

EntityCreationListener

This listener (comparable to EntityFacadeListener#entityCreating) is meant to be used by framework code and will be called before all EntityFacadeListener which are supposed to be used by business code.

Deleted entities

Similar considerations need to be made when deleting multiple entities. Entities that are being referenced by other deleted entities must be deleted first to avoid constraint violation errors (which is the reverse order of the insert).

The deletion is done by the EntityDeletionUtils based on a list of entity models.

A dependency map is created based on the existing relations between the entity models (a Multimap<EntityModel, EntityModel>). All entities of the key entity models must be deleted after the entities of the value entity models.

The following principles apply:

  • The entities on the many-to-one side of a bi-directional association need to be deleted first.

  • The owning side of a many-to-many should be deleted first (because the owning side manages the join table).

  • If the models depend on each other (many-to-one association from both sides) the side which has the non-nullable foreign key needs to be deleted first.

Note

The inserting and deleting code cannot use the same ordering logic. See comments in the issue TOCDEV-312 for more details.

For performance reasons all deleted entities (either through Entity#delete() or through the delete query builder) are collected and then deleted using a single statement. Based on the ordering explained above, the following is executed per model:

The entities are deleted using a CriteriaDelete query.

Because this deletes the entities directly from the database, we need to remove the deleted entities from the session manually. First the entities are removed from loaded collections in the session (see DeleteEntityHelper#removeFromLoadedCollections()) and then the entities itself are detached from the session.

And finally the after commit event must be manually triggered as well (see AfterCommitListener#registerEntityDeletedEvent()).

Before any delete query is executed, the session must be flushed to make sure that all UPDATE statements are executed first (as they might reference an entity that will be deleted in the same transaction and because the CriteriaDelete queries are executed immediately and not when the session is flushed).

Removal of deleted entities from associations

Before an entity is deleted, all nullable references to this entity will be set to NULL to avoid constraint violations. This also applies for batch deletions.

This was introduced in order to be compatible with existing code (as it is the default behaviour of the old persistence framework). For each ‘one to many’ association (whose inverse side is nullable) the following query is executed: UPDATE nice_entity SET relReverse = NULL WHERE relReverse = IN (:obj) (:obj are the entities to be deleted).

Note

If the entity model to be deleted has an association onto itself, the rows that will be deleted anyway will not be updated, as this caused some CHECK constraints to fail in some special circumstances.

Note

It is important that these queries are executed directly before the delete statements are executed (instead of for example doing it in DeleteEventListener.) Otherwise the NULL values might be overridden by an update statement.