Entity lifecycle callback for composite attribute

For easy access and search functionality I want to persist the total amount value of all invoice entries directly in the invoice entity. I wanted to put that logic into a lifecycle callback, but when I change an entry amount only the callback for this invoice entry is fired. Is there a way to propagate the callback also to the parent entity of the composition, if the child changes?

I know I can do it in the screen onPreCommit, but I wanted to know if I can put the logic in a more generic place.

Hi Klaus,

Did you try transaction listeners?

1 Like

@belyaev Thanks, this is awesome! The more I get to know the platform, the more I love it. :slight_smile:

1 Like

@belyaev A question to the transaction listener. If I reload an entity with the entityManager during beforeCommit like entityManager.reload(entity, "invoiceEntry-txlistener-view") I sometimes run into a org.eclipse.persistence.exceptions.DatabaseException

After investigation of the problem, I see that EclipseLink will perform a flush before the entity reload. Is there a way to prevent this behaviour?

Why do you need reloading data inside the transaction? I guess all attributes are available for you when you use the entity manager, because the entity is attached to the transaction context.

1 Like

@belyaev Thanks, you are absolutely correct, I didn’t think about that.

Hi. No, no ideas so far. Could you provide an example to reproduce this, please?

testunfetchedtxlistener.zip (235.5 KB)

@belyaev Yes, steps to reproduce:

  1. Application - Invoices - Create - Ok
  2. Open created Invoice, hit “Process” button on top
  3. Error is thrown

Thanks for your help!

image

The debugger shows that the InvoiceEntry is managed, but the Invoice is not managed. You need to fetch it from the DB to attach it to the transaction context or invoke merge before trying to access unfetched attributes.

@belyaev … but then I have to call entityManager.reload and I will probably run into the problem from above: Entity lifecycle callback for composite attribute - #4 от пользователя klaus - CUBA.Platform, no? :smile:

My quote:

reload() is not nessesary, find() or merge() attach an entity to the persistence context:

public interface EntityManager {

    /**
     * Merge the state of the given entity into the current persistence context.
     * <p>If a new or patch entity (see {@code PersistenceHelper} methods) with non-null ID is passed to merge,
     * EntityManager loads the corresponding object from the database and updates it with non-null values
     * of attributes of the passed entity. If the object does not exist in the database, the passed entity is persisted
     * and returned.
     *
     * @param entity    entity instance
     * @return the instance that the state was merged to
     * @throws IllegalArgumentException if instance is not an entity or is a removed entity
     * @see com.haulmont.cuba.core.global.PersistenceHelper#isNew(Object)
     * @see com.haulmont.cuba.core.global.PersistenceHelper#makePatch(BaseGenericIdEntity)
     */
    <T extends Entity> T merge(T entity);
1 Like

Note that implicit flush would not occur if you don’t specify the view with reload.

// implicit flush
entity = entityManager.reload(entity, "invoiceEntry-txlistener-view")

// NO implicit flush, all necessary references will be fetched lazily when inside of transaction
entity = entityManager.reload(entity)
1 Like

@belyaev @AlexBudarov Awesome, thanks!

Is the problem resolved?

Yes, in the transaction listener I implemented a helper function for entities, where the managed state is uncertain:

    fun <T : Entity<*>> reloadIfDetached(entityManager: EntityManager, entity: T): T {
        if (entityStates.isManaged(entity)) {
            return entity
        }
        return entityManager.reload(entity) ?: entity
    }
1 Like