Design question : service methods that can open or join a transaction, TransactionalDataManager

Hi

In the project I’m working on there are a lot of services, and for quite some of them I would like to be able to call the methods either:

  • through joining the current transaction (while being in a @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) call stack)
  • or open a new transaction (while being in a @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) call stack)

Knowing that UI is always opening a new transaction through DataManager, so it is not an issue there.

Let’s see an example.

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void afterCommit(EntityChangedEvent<Customer, UUID> event) {
        if (enableCommitTriggers) {
            Id<Customer, UUID> entityId = event.getEntityId();
            if (event.getType() != EntityChangedEvent.Type.DELETED) {
                commentTemplateProcessorService.updateCustomerDocumentCommentsForDeliveryById(List.of(event.getEntityId()));

    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    public void beforeCommit(EntityChangedEvent<Customer, UUID> event) {
        if (enableCommitTriggers) {
            Id<Customer, UUID> entityId = event.getEntityId();
            if (event.getType() != EntityChangedEvent.Type.DELETED) {
                commentTemplateProcessorService.updateCustomerDocumentCommentsForDeliveryById(List.of(event.getEntityId()));

[...]

Wanting the method updateCustomerDocumentCommentsForDeliveryById call to work in both cases, I came with this design.

    @Inject
    private ExtTransactionalDataManager txDm;

    @Override
    public int updateCustomerDocumentCommentsForDeliveryById(List<Id<Customer, UUID>> ids) {
        List<Customer> customers = txDm.load(Customer.class)
                .view(View.LOCAL)
                .ids(Ids.getValues(ids))
[...]

ExtTransactionalDataManager is checking whether there is an open transaction, then join it or create a new one if none exists.

public class ExtTransactionalDataManager extends TransactionalDataManagerBean {

    @Override
    public <E extends Entity<K>, K> FluentLoader<E, K> load(Class<E> entityClass) {
        return !isInTransaction(entityClass)
                ? dataManager.load(entityClass)
                : super.load(entityClass);
    }

    protected boolean isInTransaction(Class<?> cls) {
        MetaClass metaClass = metadata.getClassNN(cls);
        return isInTransaction(metaClass);
    }

    protected boolean isInTransaction(MetaClass metaClass) {
        String storeName = metadata.getTools().getStoreName(metaClass);
        return isStoreInTransaction(storeName);
    }

    protected boolean isStoreInTransaction(String storeName) {
        try {
            if (storeName != null) {
                persistence.getEntityManager(storeName);
            } else {
                persistence.getEntityManager();
            }
            return true;
        } catch (IllegalStateException x) {
            return false;
        }
    }

I’m not fond of that because creating an EntityManager just for checking is probably hitting the perf. Initially I was using persistence.isInTransaction() but I found out it does not work in all cases to know if there is actually an open transaction.

So I would be glad to know how others might have adressed this point.

Regards
Michael

Hello Michael,

Unfortunately, inside BEFORE_COMMIT event TransactionAspectSupport.currentTransactionStatus().isCompleted() == false and TransactionSynchronizationManager.isActualTransactionActive()) == true while actually transaction is not usable anymore.
So, I cannot see any reliable simple way without potential performance problems to automatically determine whether we need to create a new transaction.

As stated in docs, new transaction is always required to load data inside after commit event listener, so maybe it is better to wrap each @TransactionalEventListener[(phase = TransactionPhase.AFTER_COMMIT)] callback code with a new transaction?

Regards,
Dmitry