Mostrando postagens com marcador EJB. Mostrar todas as postagens
Mostrando postagens com marcador EJB. Mostrar todas as postagens

sexta-feira, 27 de outubro de 2017

Cuidado com Try/Catch no EJB e Spring!

Ao criarmos um método geralmente colocamos blocos de código dentro de Try/Catch para podermos tratar os possíveis erros e o sistema continuar seu trabalho. Este é talvez o principal problema de integridade e consistência de dados quando as transações são usadas. Veja o exemplo a seguir:

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public TradeData placeTrade(TradeData trade) throws Exception {
    try {
        insertTrade(trade);
        updateAcct(trade);
        return trade;
    } catch (Exception up) {
       //log the error
       throw up;
    }
}

Suponha que a conta não tenha fundos suficientes para comprar o estoque em questão ou não está configurado para comprar ou vender estoque ainda e lança uma exceção verificada (por exemplo, FundsNotAvailableException). A ordem comercial – insertTrade(trade) - persistiu no banco de dados ou a unidade lógica inteira do trabalho foi revertida? A resposta, surpreendentemente, é que, após uma exceção verificada (no Spring Framework ou EJB), a transação compromete qualquer trabalho que ainda não tenha sido comitado. Isso significa que se uma exceção verificada ocorrer durante o método updateAcct() , a ordem comercial foi persistida, mas a conta não foi atualizada para refletir a transação financeira.

As exceções de tempo de execução (ou seja, exceções não verificadas) forçam automaticamente a transação EJB/Spring a reverter completamente, mas as exceções verificadas não. Isso não é de todo ruim ou errado, pois muitas vezes precisamos tratar os erros e deixar o sistema prosseguir. Por exemplo, se em um processo, precisamos enviar um e-mail com as operações salvas no banco, mas o servidor de e-mail estiver fora, qual o mais importante? Salvar os dados ou enviar o e-mail? Então tratamos o envio do e-mail e deixamos os dados serem salvos.

Mas e quando for o caso em que precisarmos abortar tudo?

A anotação @TransactionAttribute encontrada na especificação EJB não inclui diretivas para especificar o comportamento de reversão. Em vez disso, você deve usar o método SessionContext.setRollbackOnly() para marcar a transação para rollback em um local específico, ou, o mais aconselhavel é criar uma exceção que force esse rollback com a anotação @ApplicationException setando  a propriedade rollback = true. Vamos ver primeiro ver como fazer a exceção funcionar apenas para um método:

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public TradeData placeTrade(TradeData trade) throws Exception {
    try {
       insertTrade(trade);
       updateAcct(trade);
       return trade;
    } catch (Exception up) {
        //log the error
        sessionCtx.setRollbackOnly();
        throw up;
    }
}

Uma vez que o método setRollbackOnly() é invocado, você não pode mudar de idéia; O único resultado possível é reverter a transação após a conclusão do método que iniciou a transação.

           Para pegar a sessão em um EJB faz-se daseguinte maneira:

@Resource
SessionContext context;
 

           A segunda opção que é criar uma Exceção e anotá-la segue abaixo:

@ApplicationException(rollback=true) 
public class RegraNegocioException extends Exception
{
   ...
}
 
Já no Spring Framework você especifica esse comportamento do rollback através do parâmetro rollbackFor na anotação @Transactional, conforme mostrado a seguir:

@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
public TradeData placeTrade(TradeData trade) throws Exception {
    try {
           insertTrade(trade);
updateAcct(trade);
           return trade;
    } catch (Exception up) {
           //log the error
           throw up;
    }
}

Esses são problemas difíceis de serem detectados no momento do desenvolvimento e de testes, pois geralmente ocorrem quando o processamento da aplicação e o acesso ao banco de dados estão muito altos.

Mas atenção! Essa solução funciona se você tomar cuidado com as armadilhas do REQUIRED_NEW, como mostro nesse outro post:

http://olamundo-java.blogspot.com.br/2017/10/armadilhas-do-requirednew-transaction.html

        Espero que esse post ajude. A fonte pela qual me baseei para escrever esse post segue abaixo:



quarta-feira, 25 de outubro de 2017

Armadilhas do REQUIRED_NEW transaction no EJB e Spring

Pesquisando sobre um erro de duplicação de dados e problemas com transações no EJB encontrei essa explicação muito interessante. O atributo REQUIRED_NEW tanto no EJB quanto no Spring podem ter resultados inesperados e levar a dados corrompidos e inconsistentes. Isso pode ocorrer devido o fato de ele sempre iniciar uma nova transação quando o método é chamado, mesmo já existindo uma transação ou não.

Veja o Exemplo:

@Transactional(propagation=Propagation.REQUIRES_NEW)
public long insertTrade(TradeData trade) throws Exception
{
          ...
}

@Transactional(propagation=Propagation.REQUIRES_NEW)
public void updateAcct(TradeData trade) throws Exception
{
   ...
}

Nesse caso os 2 métodos são públicos e estão anotados com REQUIRED_NEW. Quando chamados de forma separada, não há problema. Porém, se um for usado os dois métodos dentro de uma mesma unidade de trabalho transacionada, como por exemplo um outro método, isso pode causar inconsistências. Por exemplo, digamos que dentro do método insertTrade() você chame o método updadeAcct(), se ocorrer um rolled back depois do chamada do updateAcct(), ele já vai ter efetuado o commit no banco de dados, sendo desfeito apenas o que pertence ao insertTrade().

@Transactional(propagation=Propagation.REQUIRES_NEW)
public long insertTrade(TradeData trade) throws Exception {
       em.persist(trade);
       updateAcct(trade);
      //exception occurs here! Trade rolled back mas updateAcct(trade) não!
      ...
}



Isso acontece porque uma nova transação é iniciada no método updateAcct() criando uma árvore de transações, e houve um commit quando ele terminou sua execução, independentemente do que ocorra em outra transação da árvore. Devido a esse comportamento, o atributo de transação REQUIRES_NEW deve ser usado somente se a ação do banco de dados no método que está sendo invocado precisa ser salva no banco de dados independentemente do resultado da transação do topo da árvore (como uma funcionalidade de auditoria por exemplo).

O ponto principal é sempre usar o atributo MANDATORY ou REQUIRED vez de REQUIRES_NEW menos que você tenha um motivo para usá-lo.

         Esses são problemas difíceis de serem detectados no momento do desenvolvimento e de testes, pois geralmente ocorrem quando o processamento da aplicação e o acesso ao banco de dados estão muito altos.

          Aconselho uma passada por esse outro post que deixo o link logo abaixo também, pois traz um outro problema que pode acarretar em erros semelhantes de inconsistência de dados na aplicação.