Controle de transação com Spring Framework

Um dos requisitos de maior importância no desenvolvimento de uma aplicação é a consistência das informações salvas no banco de dados.

Para garantir que o banco de dados esteja consistente é fundamental que a aplicação tenha um controle de transação robusto ao manipular qualquer tipo de informação do banco de dados.

Um controle de transação deve compreender quatro operações que formam a sigla ACID: Atomicidade, Consistência, Isolação e Durabilidade.

Atomicidade: A transação com o banco de dados deve ser tratada como uma única operação, ou seja, todos os comandos realizados devem ser executados com sucesso, caso algum comando resulte em erro a operação deve ser desfeita;

Consistência: Se as operações realizadas na transação foram executadas com sucesso a base de dados deve estar consistente.

Isolação: Quando vários usuários executam uma aplicação ao mesmo, temos inúmeras transações diferentes sendo executadas no mesmo instante. O conceito de isolação garante que as transações sejam independentes, evitando que códigos executados de forma concorrente quebre a integridade dos dados.

Durabilidade: Quando um transação for finalizada/persistida os dados devem estar gravados na base não devendo ser excluídos por falhas de rede, na aplicação ou de outros sistemas.

Após compreendermos o conceito de transação vamos analisar como podemos implementar um controle de transação em nossas aplicações utilizando o Spring Framework.

O Spring oferece duas formas de trabalhar com transações: Declarativa e programática.

Na forma programática o desenvolvedor deve definir via código (por isso essa forma é chamada de programática) onde começa e termina o controle de transação. Já a forma declarativa, criamos regras que o servidor de aplicação irá seguir para delimitar o escopo, início e fim, de uma operação transacional.

A forma declarativa pode ser realizada por anotações ou configurada via arquivo XML. Nesse post, daremos ênfase na forma declarativa usando as anotações do Spring.

Para utilizar anotações para realizar o controle de transação precisamos habilitar no arquivo applicationContext.xml o uso de anotações. Essa configuração é bastante simples como mostra a Listagem 1.

<tx:annotation-driven transaction-manager="transactionManager"/>
Listagem 1: Habilita o uso de anotações para o controle de transação

Com essa configuração informamos ao servidor de aplicação que todos os conceitos de transação, que vimos no início deste post, devem ser empregados nos beans que estejam anotados com @Transactional em nível de classe ou em seus métodos.

Na Listagem 2, temos um exemplo de uso da anotação @Transactional em nível de classe.

          @Service
          @Transactional
          public class ClienteService {
         
            @PersistenceContext
            private EntityManager manager;

            public void salvarCliente(Cliente cliente) {
                 manager.persiste(cliente);
            }

            public void removerCliente(Cliente cliente) {
                manager.remove (cliente);
            }
         }
Listagem 2: Definindo os métodos do bean como transacionais

Nesse exemplo, ao inserirmos a anotação @Transactional em nível de classe, dizemos ao servidor de aplicação que todos os métodos públicos dessa classe são transacionais.

Configurando a Propagação das Transações

A política de propagação de transações é um parâmetro muito importante o qual define os limites de uma transação dentro de uma classe ou método. Com o Spring Framework, podemos definir a propagação por meio de constantes presentes na classe TransactionDefinition.

Um exemplo de configuração de propagação pode ser observado na Listagem 3.

         @Transactional(propagation = Propagation.REQUIRED)
         public void removerCliente(Cliente cliente) {
            manager.remove (cliente);
         }
Listagem 3: Difinindo a propagação do tipo Required

No método removerCliente() definimos que a propagação é do tipo REQUIRED. Mas o que isso quer dizer? Bom, antes de responder essa pergunta, devemos salientar que existem outros tipo de propagação de transação. São eles:

PROPAGATION_SUPPORTS: Com esse parâmetro configuramos o método para usar uma transação corrente caso exista, se não existir executa sem transação.

PROPAGATION_NOT_SUPPORTED: O método é sempre executado sem transação. Caso seja executado a partir de um outro método que seja transacional, a transação corrente é suspensa até que o método marcado com PROPAGATION_NOT_SUPPORTED termine sua execução.

PROPAGATION_MANDATORY: O método só é executado dentro de uma transação, se não existir uma transação quando o método for executado uma exceção será lançada.

PROPAGATION_NESTED: Caso o método seja executado a partir de uma transação, uma transação aninhada será criada. Se não existir uma transação quando o método for executado uma nova transação será criada.

PROPAGATION_NEVER: Com esse parâmetro, o método nunca pode ser executado dentro de uma transação. Se uma transação existir quando o método for executado será lançada uma exceção.

PROPAGATION_REQUIRES_NEW: Uma nova transação sempre será criada, ou seja, o método sempre será executado dentro de sua própria transação. Se uma transação existir quando este método for executado, esta será suspensa até que o método termine sua execução.

PROPAGATION_REQUIRED: Tipo de propagação utilizada no exemplo da Listagem 3 na qual o método é executado em uma transação. Se o método removerCliente() for invocado a partir de outro método que possua uma transação, o removerCliente() fará parte dessa transação. Caso seja invocado por um método que não seja transacional, uma nova transação será criada.

Uma observação importante é que se não definirmos nenhuma política de transação para nossos métodos o valor PROPAGATION_REQUIRED será assumido, já que é a política de propagação de transação padrão.

Outro parâmetro existente na anotação @Transactional é readOnly, que pode ser definido como true ou false, sendo esse último o valor padrão quando esse parâmetro não for configurado na anotação. Quando definimos readOnly com o valor true, como mostra a Listagem 4, estamos informando ao servidor que dentro do método buscarClientes() apenas consultas serão realizadas. Caso alguma operação de inserção, atualização ou remoção seja realizada dentro do método anotado com readOnly = true, uma exceção será lançada.

A configuração desse parâmetro é importante para ganharmos performance na execução do método. O Spring, ao identificar que a transação é somente leitura (readOnly) uma otimização de acesso aos dados da base é realizada.

         @Transactional(readOnly = true)
         public List buscarClientes() {
             manager.findAll();
         }
Listagem 4: Criando uma transação somente leitura

Rollback de transações

A operação de rollback é de extrema importância em um ambiente transacional, pois se algum erro acontecer durante o processo de manipulação de dados, tudo que alteramos na base de dados deve ser desfeito.

Por padrão, em transações declarativas qualquer exceção de tempo de execução (Runtime) quando lançada executa uma operação de rollback.

Entretanto, podemos configurar para que exceções de negócio quando disparadas também executem um rollback na transação, como mostra a Listagem 5.

         @Transactional(propagation = Propagation.REQUIRED, rollbackFor = BusinessException.class)
         public void atualizarCliente(Cliente cliente) throws BusinessException {
              if (cliente.getStatus().equals("CANCELADO")) {
                  throw new BusinessException("Não é permitido atualizar um  cliente com status cancela");
              }
              manager.merge(cliente);
        }
Listagem 5: Configurando rollback com exceção de negócio

Podemos observar que configuramos no parâmetro rollbackFor da anotação @Transactional a classe BusinessException.class.

Com isso, informamos ao servidor que se essa exceção for disparada uma operação de rollback deve ser realizada desfazendo todas as alterações realizadas no banco de dados.

Neste post, apresentamos os principais conceitos de transação e como o Spring Framework oferece suporte a Controle de Transações. Naturalmente, esse é um assunto vasto que não poderia ser abordado por completo em um único post, assim convido o leitor a se aprofundar nesse assunto lendo a documentação do Spring e praticando. Bons estudos!

REFERÊNCIAS

http://spring.io/docs/reference

Documentação de referência do Spring.

Por EDMAR BREGAGNOLI

Postado em: 18 de novembro de 2015

Confira outros artigos do nosso blog

Inteligência Artificial: uma introdução ao Deep Learning

11 de setembro de 2018

Guilherme Moraes Armigliatto

Implementando Circuit Breaker com Spring Cloud Netflix

25 de julho de 2018

Jamila Peripolli Souza

Balanceamento de carga em microsserviços com Spring Cloud Netflix

13 de julho de 2018

Jamila Peripolli Souza

Quais os benefícios da arquitetura REST?

26 de junho de 2018

Henrique Lacerda

Deixe seu comentário