Transactional control in context of exception handling using Demoiselle

0

Hello,

I am having second thoughts about transactional control via Demoiselle (@Transactional) in the exception handling context with the Demoiselle ExceptionHandlerInterceptor.

I have the following web service:

import br.gov.frameworkdemoiselle.transaction.Transactional;
import br.gov.frameworkdemoiselle.exception.ExceptionHandler;
import br.gov.frameworkdemoiselle.stereotype.Controller;

@WebService
@Controller
public class SislvServiceImpl implements SislvService {

    @Override
    @WebMethod
    @Transactional
    public RegistrarLaudoRetorno registrarLaudo(SolicitanteHeader solicitanteHeader, RegistroLaudoRequest laudoRequest)
            throws MalformedMessage, InternalServerError, Unauthorized, LaudoRejeitado {

        OperacaoRegistrarLaudo op = Beans.getReference(OperacaoRegistrarLaudo.class);
        return op.registrarLaudo(solicitanteHeader, laudoRequest);
    }

    @ExceptionHandler
    public void excecao(Exception e) throws Exception {
        SislvExceptionHandler handler = Beans.getReference(SislvExceptionHandler.class);
        handler.handle(e);
    }

}

When running op.registrarLaudo() , at some point the following method will be executed in order for the WS call to be properly audited:

private void auditarNoBancoSislv() {
    AuditoriaRequisicaoWs auditoriaRequisicaoWs = auditoriaRequisicaoWsFactory.createAuditoriaRequisicaoWs();
    auditoriaRequisicaoWsDAO.insert(auditoriaRequisicaoWs);
}

In this case, the happy way, everything happens correctly!

But if the op.registrarLaudo() method throws an exception, then we will have the exception handler handling in SislvServiceImpl and the execution of the handler.handle() method. At some point the 'handler.handle ()' method will also execute the auditarNoBancoSislv() audit method. The problem is that since we had an exception that interrupted the execution of sislvServiceImpl.registrarLaudo() , the open transaction in sislvServiceImpl.registrarLaudo() will not be effective, so the auditarNoBancoSislv() method will not take effect: the audit will not be written to the database! / p>

My attempt to work was as follows, change method excecao() to

    @ExceptionHandler
    @Transactional
    public void excecao(Exception e) throws Exception {
        SislvExceptionHandler handler = Beans.getReference(SislvExceptionHandler.class);
        handler.handle(e);
    }

The idea is that if an exception occurs in sislvServiceImpl.registrarLaudo() , Demoiselle will sort the transaction (TransactionalInterceptor) rollback and will also execute my sislvServiceImpl.excecao() (ExceptionHandlerInterceptor) method. Then when the sislvServiceImpl.excecao() method is executed, a new transaction would open so we can write things to the bank, since the previous transaction would have already been closed. But it did not work! = (

Another attempt was as follows:

@Transactional
private void auditarNoBancoSislv() {
    AuditoriaRequisicaoWs auditoriaRequisicaoWs = auditoriaRequisicaoWsFactory.createAuditoriaRequisicaoWs();
    auditoriaRequisicaoWsDAO.insert(auditoriaRequisicaoWs);
}

In this case the logic would be: for the exceptional path, when you reach this in auditarNoBancoSislv() there is no active transaction, then a new transaction is opened, which did not work !!! Now for the happy way, the Demoiselle would have to realize that there is already an active transaction, and simply do nothing for the code to take advantage of the already active transaction, which worked, although I do not know if exactly as described.

In both attempts, I annotated with the DemoiselleController the classes containing the methods annotated with @Transactional. And in both cases the objects containing the methods annotated with @Transactional are instantiated via CDI.

And last, but not least, my beans.xml :

<beans ...>
    <interceptors>
        <class>br.gov.frameworkdemoiselle.exception.ExceptionHandlerInterceptor</class>
        <class>br.gov.frameworkdemoiselle.transaction.TransactionalInterceptor</class>
    </interceptors>
</beans>

Note: the rollback behavior when an exception happens is what I want for the application. It is only at the moment of the audit that I want to make the recording in the bank under any circumstances.

Finally, the help I need is to find a way to persist database data by invoking DAO in the exceptional path, which started to be executed by a method invoked by Demoiselle's ExceptionHandlerInterceptor shortly after an exception has stopped a method annotated with the Demoiselle @Transactional.

Thank you for your attention!

Leonardo Leite

=================

Later edition: retried.

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

public class Auditor {

    @Inject
    private EntityManagerFactory entityManagerFactory; 

    ...

    private void auditarNoBancoSislv() {
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        AuditoriaRequisicaoWsDAO auditoriaRequisicaoWsDAO = new AuditoriaRequisicaoWsDAO(entityManager);
        AuditoriaRequisicaoWs auditoriaRequisicaoWs = auditoriaRequisicaoWsFactory.createAuditoriaRequisicaoWs();
        entityManager.getTransaction().begin();
        auditoriaRequisicaoWsDAO.insert(auditoriaRequisicaoWs);
        entityManager.getTransaction().commit();
    }

}

It did not work. Error:

16:22:15,319 ERROR [br.gov.serpro.siscsvws.SiscsvExceptionHandler] (http-/0.0.0.0:8443-1) Erro interno inesperado.: java.lang.IllegalStateException: A JTA EntityManager cannot use getTransaction()
    at org.hibernate.ejb.AbstractEntityManagerImpl.getTransaction(AbstractEntityManagerImpl.java:1019) [hibernate-entitymanager-4.2.14.SP1-redhat-1.jar:4.2.14.SP1-redhat-1]
    at br.gov.serpro.siscsvws.auditoria.Auditor.auditarNoBancoSislv(Auditor.java:48) [classes:]

Line 48 is the entityManager.getTransaction().begin(); .

Additional information, persistence.xml :

<persistence ...>

    <persistence-unit name="siscsv-ds" transaction-type="JTA">
        <jta-data-source>java:jboss/datasources/SiscsvDS</jta-data-source>

        <class>br.gov.serpro.siscsv.entity.auditoria.AuditoriaRequisicaoWs</class>
        ....

        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
            <property name="hibernate.default_schema" value="siscsv" />
            <property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform" />

        </properties>
    </persistence-unit>
</persistence>
    
asked by anonymous 24.02.2016 / 12:46

2 answers

1

In summary, the way I resolved it was to stop using the @Transactional annotation and doing the transactional control through the UserTransaction interface.

In terms of code, the result was somewhat equivalent to:

import br.gov.frameworkdemoiselle.exception.ExceptionHandler;
import br.gov.frameworkdemoiselle.stereotype.Controller;

@WebService
@Controller
public class SislvServiceImpl implements SislvService {

    @Inject
    private MyTransactor transactor;

    @Override
    @WebMethod
    public RegistrarLaudoRetorno registrarLaudo(SolicitanteHeader solicitanteHeader, RegistroLaudoRequest laudoRequest)
            throws MalformedMessage, InternalServerError, Unauthorized, LaudoRejeitado {

        transactor.begin();
        OperacaoRegistrarLaudo op = Beans.getReference(OperacaoRegistrarLaudo.class);
        RegistrarLaudoRetorno retorno = op.registrarLaudo(solicitanteHeader, laudoRequest);
        transactor.commit();
        return retorno;
    }

    @ExceptionHandler
    public void excecao(Exception e) throws Exception {
        transactor.rollback();
        transactor.begin();
        SislvExceptionHandler handler = Beans.getReference(SislvExceptionHandler.class);
        Exception e = handler.handle(e);
        transactor.commit();
        throw e;
    }

}


========================================


import javax.transaction.UserTransaction;

public class MyTransactor {

    @Inject
    private UserTransaction userTransaction;

    public void begin() {
        try {
            userTransaction.begin();
        } catch (NotSupportedException | SystemException e) {
            throw new IllegalStateException("Não consegui iniciar transação", e);
        }
    }

    public void commit() {
        try {
            userTransaction.commit();
        } catch (SecurityException | IllegalStateException | RollbackException | HeuristicMixedException
                | HeuristicRollbackException | SystemException e) {
            throw new IllegalStateException("Não consegui finalizar transação", e);
        }
    }

    public void rollback() {
        try {
            userTransaction.rollback();
        } catch (IllegalStateException | SecurityException | SystemException e) {
            throw new IllegalStateException("Não consegui cancelar transação", e);
        }
    }

}

I even made attempts with the @TransactionAttribute annotation, but I did not succeed. So I kept the option of "manual" control of the transaction. But it's working.

    
15.03.2016 / 19:36
1

The problem occurs because since the registrarLaudo method is annotated with @Transactional , all methods invoked from it are nested in the same transaction. So a rollback at any point in the operation will prevent commit from all nested transactions.

To resolve this situation, you must create another (independent) transaction for the audit logs.

Java EE 7's ManagedExecutorService brings what it seems be an excellent alternative to this need. Here has an example of how this feature can be used.

    
16.03.2016 / 13:06