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>