Transactional control in the context of exception handling using Demoiselle

Asked

Viewed 476 times

0

Hello,

I have questions about transactional control via Demoiselle (@Transactional) in the context of handling exceptions with the Exceptionhandlerinterceptor of Demoiselle.

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 executing op.registrarLaudo(), at some point the following method will be executed so that the call to WS is properly audited:

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

In this case, the happy way, everything goes right!

But if the method op.registrarLaudo() triggers some exception, then we will have the treatment done by Exception Handler in Sislvserviceimpl and the execution of the method handler.handle(). At some point the 'Handler.Handle()' method will also execute the audit method auditarNoBancoSislv(). The problem is that as we had an exception we interrupted the execution of sislvServiceImpl.registrarLaudo(), the transaction opened in sislvServiceImpl.registrarLaudo() will not be effective, and thus the method auditarNoBancoSislv() does not take effect: the audit will not be recorded in the database!

My attempt at solution was the following, change the method excecao() for

    @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 order the rollback of this transaction (Transactionalinterceptor) and will also execute my method sislvServiceImpl.excecao() (Exceptionhandlerinterceptor). There when the method sislvServiceImpl.excecao() If executed, a new transaction would be opened so that we could record things in the bank, since the previous transaction would have already been closed. But it didn’t work! =(

Another attempt was the following:

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

In this case the logic would be: for the exceptional path, when arriving at this in auditarNoBancoSislv() there is no active transaction, then a new transaction opens, which did not work!!! For the happy path, 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.

On both attempts, I wrote down with @Demoiselle Controller 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, man 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. Only at the time of the audit do I want to make the recording in the bank under any circumstances.

Finally, the help I need is to find a way to persist the database data by invoking the DAO on the exceptional path, which began to be executed by a method invoked by the Exceptionhandlerinterceptor of Demoiselle soon after an exception interrupted a method annotated with the Demoiselle @Transactional.

Grateful for the attention!

Leonardo Leite

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

Later edition: new attempt.

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 didn’t work. Mistake:

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>
  • 1

    Note: Demoiselle version: 2.5.0.

2 answers

1


In short, the way I solved was to stop using the @Transactional annotation and transactional control through the Usertransaction interface.

In terms of code, the result was something 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 was unsuccessful. That’s why I kept the option for "manual" control of the transaction. But it’s working.

1

The problem occurs because once the method registrarLaudo is noted with @Transactional, all methods invoked from it will be nested in the same transaction. Therefore, a rollback at any time of the operation will prevent the commit of all nested transactions.

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

The appeal Managedexecutorservice of Java EE 7 brings what seems to be an excellent alternative to this need. Here has an example of how this resource can be used.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.