Pesquisar este blog

segunda-feira, 12 de outubro de 2015

Controlando Transações do Banco de dados no Java com o Spring Transaction

Seguindo a construção do nosso sistema de cadastro de usuários, devido a estrutura normalizada do nosso banco de dados, ao registrar um usuário, deveremos gravar os dados da entidade usuário (nome e email) em uma tabela, e da entidade usuarioSenha (senha do usuário) em outra tabela. Mas como fazer isso sem que tenhamos algum problema no meio do caminho?
Um problema clássico ao gravarmos dados em duas tabelas dentro de uma mesma operação é acontecer algum problema (exceção), após os dados terem sido salvos na primeira tabela. Com isso, ao não transacionar a gravação, os dados serão salvos na primeira tabela e não serão salvos na segunda, fazendo com a que a base de dados fique inconsistente, uma vez que os dados como nome e email do usuário foram gravados, porém a sua senha não. E o primeiro sintoma disto é a impossibilidade do usuário logar no seu sistema.
E como resolver este problema?

@Transações em Banco de Dados

O conceito das transações em banco de dados é descrito pela sigla ACID (Atomicidade, Consistência, Isolamento e Durabilidade)
  • Atomicidade: A transação deve ser considerada como uma sequência de operações única, o que significa que ou todos os dados serão gravados de uma vez só, executando as operações nas múltiplas tabelas, ou nenhum dado será gravado no banco, quando houver algum problema, ou caso alguma validação falhe.
  • Consistência: Que representa a consistência dos dados gravados no banco, como a unicidade de uma chave primária, a existência das referências e chaves estrangeiras, etc
  • Isolamento: Podem existir muitas transações sendo processadas ao mesmo tempo no banco de dados, porém elas devem estar isoladas de maneira que uma não interfira na outra.
  • Durabilidade: Assim que uma transação completa, os seus dados devem estar armazenados no banco, de maneira que eles não podem mais ser apagados/corrompidos, devido a uma falha do sistema do banco.
Um sistema de armazenamendo RDMS garante todas as propriedades descritas acima. No SQL os comandos usados para transação são:
  • Begin: inicia uma transação
  • Commit: grava os dados de todas as operações da transação no banco
  • Rollback: desfaz as operações da transação e o banco retorna ao estado em que ele estava antes de a transação iniciar

Transações Globais e Locais

As transações locais, envolvem apenas uma conexão com o banco de dados, enquanto uma transação global pode afetar múltiplos sistemas, como um banco distribuído. 
Um sistema de transação local, possui apenas uma máquina com o banco de dados e por isso são mais fáceis de serem administradas, já um sistema global pode ter múltiplas máquinas espalhadas por várias localidades diferentes.

Transacionando os seus métodos

E como fazer para transacionar as operações com o banco de dados?
O Spring Transaction possui uma anotação chamada @Transactional esta anotação deverá ser colocada nos métodos que contém o código com a regra de negócio de alteração do banco.
        @Transactional
public void gravaUsuario(Usuario usuario, UsuarioSenha usuarioSenha) {
usuarioSenha.setUsuario(usuario); // Por que esta linha?
repositorioUsuarioSenha.save(usuarioSenha);
}
Com isso todas as operações dentro deste método serão encapsuladas dentro de uma transação no banco de dados.
Repare que há uma pequena regra de negócio neste método que é a alteração do id do usuário senha depois do usuário ter sido inserido no banco de dados. Por que esta linha é necessária?
Esta linha está criando o relacionamento das duas entidades.
Mas por que não estamos gravando a entidade usuário?
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
private Usuario usuario;
Na definição do relacionamento das duas entidades estamos marcando o tipo de cascata (cascade = CascadeType.ALL) ou seja, todas as operaçoes realizadas em uma entidade, também serão realizadas na outra, portanto, ao gravar os dados de usuarioSenha, os dados do usuário também serão gravados.
Entenda que todas as operações realizadas dentro do método gravaUsuario, serão feitas dentro da mesma transação, portanto, uma outra opção de gravação dos dados seria:
        @Transactional
public void gravaUsuario(Usuario usuario, UsuarioSenha usuarioSenha) {
repositorioUsuario.save(usuario);
usuarioSenha.setUsuario(usuario); // Por que esta linha?
repositorioUsuarioSenha.save(usuarioSenha);
}
Mesmo fazendo assim, devido a anotação @Transactional, todas as operações deste método estariam dentro da mesma transação.

Transações encapsuladas

Tudo certo até agora, e transacionar uma operação com o spring é bem tranquilo, mas o que acontece quando um método transacionado, chama um outro método transacionado?
Isto pode acontecer quando um método que cria uma outra entidade e também criará um usuário, o que pode acontecer por exemplo em uma integração de sistemas. Este caso é chamado de nested transaction e existem algumas formas de tratarmos isto.
O tratamento destes casos é feito pela propriedade propagation, e existem alguns tipos de propagação de transação:

  • Mandatória: Propagation.MANDATORY - necessita que uma transação já tenha sido criada, caaso não tenha nenhuma transação ele dispara uma exceção TransactionRequiredException. Só deve ser usado em métodos que serão utilizados somente dentro de outros métodos transacionais.
  • Aninhada: Propagation.NESTED - O bloco de código será executado dentro de uma nova transação aninhada, caso uma transação já exista, caso contrário uma nova transação será criada. Esta transação aninhada será uma parte da transação já existente e será criado um ponto de checagem antes da criação da transação aninhada. Caso a transação aninhada falhe, o banco volta ao estado anterior a criação da transação aninhada. Os dados desta transação aninhada só será comitada ao final da transação existente.
  • Necessita de uma nova - Propagation.REQUIRES_NEW - uma transação nova e independente será criada ao início deste método. A transação anterior será pausada antes do início deste método e ela irá continuar assim que a transação corrente terminar.
  • Nunca propague: Propagation.NEVER - O método não suporta uma transação e dispara uma exceção caso uma transação anterior exista.
  • Não suportada: Propagation.NOT_SUPPORTED - Este método não necessita de transação e nenhuma transação será criada nele. Caso uma transação exista, ela será suspensa durante a execução deste método.
  • Suporta: Propagation.SUPPORTS - Uma transação não é necessária neste método, se alguma transação já existir ela será usada, caso contrário não.
  • Necessária: Propagation.REQUIRED - A transação é necessária para este método, caso não tenha nenhuma transação criada, uma nova será criada, caso já exista uma transação ele utilizará ela. Este é o valor padrão de uma transação.

Somente Leitura:

Podemos utilizar uma transação somente leitura, geralmente usada para métodos de select, e para fazer isto usamos o atributo readonly:
@Transactional(readOnly=true)




    Nenhum comentário:

    Postar um comentário