What is the correct way to persist entities based on other entities being updated in the same transaction?

104 Views Asked by At

Description

I'm trying to persist JPA Entities in a Wildfly Web Application based on entities currently being updated in the same JTA transaction.

What needs to happen is when an entity of some type is being updated by the EntityManager, a custom strategy based on the entity type needs to run a custom JPQL query in order to find what records need to be additionally persisted into the database within the same transaction as the original entities that have been updated.

Things I tried

First Try

The thing I tried is using javax.persistence events such as @PreUpdate event in order to gather all updated entities that I'm interested of. Since javax.persistence events do not allow the direct usage of EntityManager or Query objects, I store the entities in a map in a TransactionScoped bean. Then via CDI event, like:

 void beforeTransactionCommit(@Observes(during = TransactionPhase.BEFORE_COMPLETION) @Initialized(TransactionScoped.class) final Object ignored){
        //find records to be persisted and persist them through the EntityManager
    }

I find all the records that I need to persist based on the stored updated entities and persist them for the current transaction.

This seems to mostly work, but sometimes the javax.persistence events are called after the given CDI event, so I may not register the updated entity in time.

Second Try

The second thing I tried was again using the javax.persistence events, but instead of CDI event for listening for the transaction before completion, I use transaction Synchronization which I register via TransactionSynchronizationRegistry.registerInterposedSynchronization. In Synchronization.beforeCompletion() method implementation I find all the records that need to be created, persist them with the EntityManager and then manually flush it, as the managed flush of the EntityManager already has happened before the synchronization call.

    @Override
    public void beforeCompletion() {
        convertEntitiesToRecords();
        registry.flushRecords();

        //manual flush needed in transaction synchronization to flush the records
        entityManager.flush();
    }

The problem with this approach is that while it works, the Spec of the TransactionSynchronizationRegistry.registerInterposedSynchronization says the following:

The beforeCompletion callback will be invoked in the transaction context of the transaction bound to the current thread at the time this method is called. Allowable methods include access to resources, e.g. Connectors. No access is allowed to "user components" (e.g. timer services or bean methods), as these might change the state of data being managed by the caller, and might change the state of data that has already been flushed by another caller of registerInterposedSynchronization. The general context is the component context of the caller of registerInterposedSynchronization.

So it at least seems that using the EntityManager and TransactionScoped bean along with the registerInterposedSynchronization synchronization is against the accepted contract of usage of the method.

0

There are 0 best solutions below