Pesquisar este blog

segunda-feira, 26 de outubro de 2015

Testes de código no Java


Dependências

As dependênicias que devem ser adicionadas ao projeto para usar as ferramentas apresentadas neste post são:

<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>

Testes no desenvolvimento de software

Todos sabem que o desenvolvimento de software muitas vezes não atende plenamente o que foi especificado pelo cliente.
Existem muitos processos de desenvolvimento de software, cada um com suas vantagens e desvantagens.
Existem vários artigos científicos explicando que o custo de conserto de um bug é exponencial em relação ao tempo que ele foi descoberto. Quanto mais cedo for descoberto um bug, mas fácil é de se arrumar ele. Isto acontece devido a memória do programador, se o bug é descoberto durante o desenvolvimento, o programador tem na cabeça o funcionamento daquele código e com isso fica bem fácil de arrumar o problema. Quanto mais tempo passa, após o desenvolvimento, mais difícil será para o programador lembrar do código, com isso ele leva algum tempo até ler e entender o funcionamento do código.
Por mais legível que seja o código, haverá uma perda de tempo para ler e entender o código.
Bugs nos softwares podem ser classificados em diversas formas:
  • atende aos requisitos especificados na documentação da análise
  • responde corretamente aos dados de entrada
  • realiza suas operações em um período aceitável
  • é utilizável
  • pode ser instalado e rodar nos ambientes planejados
  • atinge os resultados que os clientes desejam
Existem várias formas de se encontrar um bug no software e também existem várias intensidades de um bug, por exemplo uma cor errada em um botão é certamente menos grave que um bug que calcula errado o saldo da sua conta no banco.
A intensidade do bug varia de negócio para negócio, mas certamente um dos objetivos de quem desenvolve software é diminuir ao máximo a quantidade de bugs nos seus sistemas.
E como fazer isso? Existem uma série de ferramentas que nos ajudam a validar a qualidade do código, e as farramentas de teste unitários são uma delas.

Teste de unidade

O teste de unidade testa as assinaturas, as entradas e saídas de uma unidade de software. Por unidade de software entenda como a menor parte testável de um software. Por exemplo em um programa orientado a objeto, um método pode ser uma unidade a ser testada.
As entradas e saídas, podem ser os parâmetros do seu método e as respostas que o seu método retorna após ser executado. 

Ferramentas de Teste Unitário para o Java

JUnit

Uma das ferramentas mais conhecidas para a execução de testes unitários é o JUnit. Esta ferramenta fornece métodos e anotações que facilitam a execução de códigos de testes.
Por exemplo 
O processo de desenvolvimento de software exerce um papel bem importante na quantidade de bugs. Existe uma técnica conhecida como Desenvolvimento Guiado por Testes, ou Test Driven Development (TDD) que prega que devemos criar primeiro um teste para depois escrevermos o código.
Por exemplo, imagina que você esteja desenvolvendo uma calculadora e o seu código está assim:
public class Calculadora {
  public int operacao(String expressao) {
    int resultado = 0;
    for (String soma: expressao.split("\\+"))
      resultado += Integer.valueOf(suma);
    return soma;
  }
}
Por enquanto esta sua calculadora só faz a soma, e claro como ela é muito simples não estamos aplicando técnicas de orientação a objeto nela, então um teste válido para ela seria:
import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class CalculadoraTest {
  @Test
  public void avaliaAExpressao() {
    Calculadora calculadora = new Calculadora();
    int soma = calculadora.operacao("1+2+3");
    assertEquals(6, soma);
  }
}
A classe de teste é uma classe java normal, a única diferença aqui é que a anotação @Teste marca quais são os métodos que vão testar o código, o que neste caso estamos avaliando uma operação de soma. O método assertEquals valida se o resultado retornado pelo método soma é igual a 6, assertEquals valida se o primeiro atributo é igual ao segundo.
Mas não temos o == para validar a igualdade, por que então é que usamos o assertEquals?
O JUnit provê vários métodos que inciam como assert e nos ajudam a validar dados, e usar eles facilita a nossa vida, pois quando uma validação falha, ele automaticamente gera um relatório daquela falha.

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class CalculadoraTest {
  @Test
  public void avaliaAExpressao() {
    Calculadora calculadora = new Calculadora();
    int soma = calculadora.operacao("1+2+3");
    assertEquals(6, soma,"Resultado da soma inválido");
  }
}
Os métodos de assert possuem um argumento de mensagem, que será colocada no log dos testes e pode nos ajudar a achar o problema no caso de uma falha.

Mock de objetos

O JUnit é muito bom para testar as regras do código que não dependem de nenhum serviço externo, como uma classe de outra camada, um banco de dados, algum webservice ou mesmo uma api de terceiros.
Mas e quando o código a ser testado depende de algo externo?
Um dos intuitos dos testes unitários é testar apenas uma parte do código, eles fazem parte do processo de desenvolvimento e devem rodar bem rápido. Quando o código depende de algo externo, um banco de dados, ou de um serviço externo, pode impactar bastante  na performance de execução de um teste de unidade. 
E como fazemos para resolver este tipo de problema?
Uma das formas de resolver isso, é usando um objeto Mock, um Mock nada mais é que um simulador de resultados, com um mock podemos simular qual vai ser o resultado de um select no banco, sem precisarmos nos conectar a ele, com isso conseguimos simular o funcionamento da parte do código que realiza operações no banco de dados, sem ter que acessá-lo.
EasyMock e JMock são dois exemplos de frameworks de mock para Java.
Chamamos de mock a instância de uma classe que vamos simular, por exemplo ao testar o validador, apresentado aqui, usariamos o mock para mockar os objetos que não são usados no teste:

public class ValidadorDeSenhaTest extends EasyMockSupport{

   private MessageFactory msg = createNiceMock(MessageFactory.class);

   @TestSubject
   private final ValidadorDeSenha validador = new ValidadorDeSenha(msg);

   @Rule
   public ExpectedException excecao= ExpectedException.none();

   @Mock
   private FacesContext context;
   @Mock
   private UIComponent uiComponent;

   @Test()
   public void testSenhaErrada() {
       String mensagem = "Mensgem";
       excecao.expect(ValidatorException.class);
       excecao.expectMessage(mensagem);
       String valor = "1234A";      
       expect(msg.getMessage("senhaInvalida")).andReturn(mensagem);
       replay(msg);
       validador.validate(context, uiComponent, valor);
   }
Esta implementação foi feita através do EasyMock, e podemos notar isso logo na extensão da classe EasyMockSupport, esta classe irá ajudar a trabalhar com os mocks.
O primeiro ponto deste teste é a utilização do método createNiceMock na classe MessageFactory, este método é utilizado para criarmos um mock da classe MessageFactory, esta classe será criada sem nenhum parametro, ou método implementado, por enquanto.
Esta classe será utilizada no construtor da classe ValidadorDeSenha, que vai ser o nosso sujeito do teste (@TestSubject), esta anotação está nos dizendo onde é que os objetos mockados serão utilizados.
O método de validação dispara uma exceção quando a validação falha, por isso usamos a anotação @Rule para criar uma regra de exceção. Dentro do método de teste este objeto, excecao, será configurado para esperar uma exceção do tipo ValidatorException, com a mensagem "Mensagem". Estas configurações foram adicionadas para validar se realmente o objeto mock foi utilizado, já que a mensagem disparada pela implementação é a mensagem configurada no arquivo de properties.
Para mockar um objeto, basta adicionarmos a anotação @Mock sobre os objetos mockados, e como o método validador recebe um objeto FacesContext e um UIComponent, que não serão utilizados na sua implementação, podemos mocka-los sem problemas.
Repare que o método expect é utilizado para dizer ao mock que haverá uma chamada ao método getMessage, com o parametro "senhaInvalida", e este método quando chamado irá retornar a mensagem "Mensagem".
Quando um mock é criado ele não possui nenhuma implementação de método e se algum método do mock for executado, ele irá disparar uma mensagem. O método expect server para simular chamadas aos métodos, podendo determinar o seu retorno e isso é que torna possível emular uma camada do nosso software, como um banco de dados.
Neste exemplo há apenas uma chamada a um método, mas poderiam ser várias, o único detalhe é chamar o método replay depois de configurar todas as chamadas a métodos, com isso o easymock implementa todas as expectativas no objeto mockado e o teste pode ser executado.

Teste de integração

O teste de integração é feito em sistemas que possuem conexões com outros sistemas, este teste visa validar se a troca de informações entre diversos sistemas acontecem como o esperado.
Imagine um sistema de banco, que deve-se conectar com o sistema de outros bancos para trocar informações das transferências interbancárias realizadas.
Estas informações trocadas entre os sistemas, devem obedecer a um padrão estabelecido pelo Banco Central, porém para o banco, é muito importante que esta troca de informações seja testada, para evitar problemas na contabilização destas operações entre bancos.

Teste de regressão

Ao trocarmos a versão de um software, algumas funcionalidades podem sofrer alterações e é pra isso que servem os testes de regressão, eles irão testar se as funcionalidades de uma versão, continuam funcionando da mesma forma, em uma versão futura do software, e ele garante que uma nova versão não irá quebrar o que já funcionava bem na versão anterior.

Processo de Desenvolvimento

Não há dúvidas que os testes são uma parte muito importante no desenvolvimento de software, mas eles em si, não garantem que todos os bugs serão eliminados. 
Em um processo de desenvolvimento de software, quando um bug for encontrado, deve-se arrumá-lo e criar um teste para aquele caso, que provavelmente não estava sendo testado antes, senão o bug não existiria. Mas se o bug já foi arrumado, por que deve-se criar um teste para ele? Assim como o software foi para produção com aquele bug uma vez, nada impede que no futuro um desenvolvedor volte a alterar aquele código, de forma a recriar o bug. O teste criado durante a correção do bug, garante que ele não vá acontecer em uma versão futura, e se acontecer o teste irá falhar gerando um alerta para o desenvolvedor.

Test Driven Development (TDD)

O TDD prega que devemos implementar primeiro os testes e depois o código, com isso o desenvolvedor consegue planejar e pensar melhor no código a ser implementado naquale funcionalidade do sistema. 
No primeiro exemplo deste post, há uma calculadora, mas esta calculadora só executa a soma. Assumindo que esta calculadora irá implementar somente as operações básicas, soma, subtração, divisão e multiplicação, usando o TDD, o primeiro passo seria implementar um teste para cada uma destas operações. Com isso todos os testes falhariam no começo, já que a implementação do código da calculadora, ainda nem começou.
Passo a passo o desenvolvedor vai implementado os métodos, de maneira que os testes passem e o seu trabalho só termina quando todos os testes passarem.
Se todas as regras de negócio da calculadora, o que neste caso eram as operações aritiméticas básicas, estiverem sendo testadas, ao final deste processo ele garante que as regras foram implementadas.
Desenvolver software usando TDD, obriga o desenvolvedor a pensar nos cenários de teste antes de implmentar a regra de negócio e com isso, quando ele for implementar as regras, ele vai fazer isto pensando em evitar os erros pré planejados nos testes. Isto faz com que a qualidade do software implementado por ele aumente.
Mas o TDD não é feito somente de vantagens, e ele também não se encaixa em todos os projetos.
Em alguns casos, quando o projeto as regras são alteradas constantemente, usar TDD pode atrapalhar, já que ficar refazendo os testes toda hora terá um custo de desenvolvimento. Além das regras, pode ser que o design do projeto também se altere algumas vezes.É claro que um projeto que as regras ou o design são alterados constantemente tende a não ser um bom projeto.
Testes de algoritmos muito complexos serão difíceis de implementar, o que pode atrazar o desenvolvimento do produto.
Alguns autores citam que o tempo adicional de se escrever o código do teste é uma desvantagem deste processo, mas isto é muito discutido uma vez que a cada bug evitado pelo teste, estará evitando um grande e moroso processo de abertura de um problema no sistema, podendo impactar na experiência do usuário.

Usar ou não?

Há uma grande discussão na comunidade de desenvolvimento des software sobre usar ou não usar testes unitários. Existem vários argumentos contra e vários argumentos a favor.
Muitas pessoas argumentam que desenvolver testes é uma perda de tempo, pois "perde-se"  muito tempo desenvolvendo os testes, quando este tempo poderia ser usado para desenvolver mais funcionalidades.
É certo que ao desenvolver testes junto com o código, gasta-se mais tempo no desenvolvimento, mas entenda que este tempo gasto a mais, certamente será compensado ao evitar erros.
Não há dúvida que fazer testes unitários poupa tempo no desenvolvimento de software, entenda que o ciclo de desenvolvimento de um software é um processo longo, e garantir que as regras de negócio estão funcionando, e continuam funcionando a cada build, é um passo importante para garantir a qualidade no processo de desenvolvimento.
O tempo gasto para se corrigir um bug, assim com o seu custo, é exponencialmente maior, quanto mais tempo se passou do desenvolvimento daquela funcionalidade, assim quanto antes descobrimos um bug, mais fácil será para arrumá-lo.
Criar testes que exercitam as regras de negócio implementadas, ajuda o desenvolvedor a descobrir erros em sua implementação e como a implementação dos testes é feita junto ao desenvolvimento do software, os bugs encontrados serão corrigidos bem mais facilmente.
Mesmo que o teste implementado não ache nenhum problema, seu tempo de implementação será compensado pela garantia de que aquela regra continuará funcionando, uma vez que uma modificação futura poderia quebrar aquela regra, o teste garante que isso não vai acontecer, e caso aconteça, o desenvolvedor será avisado.

Ferramentas de análise de código

Existem várias ferramentas de análise de código, como o PMD. Estas ferramentas fazem busca por códigos que nunca são utilizados, possíveis bugs, como as NullPointerExceptions, complexidade ciclomática das classes, relativo a quantidade de caminhos possíveis dentro do código, ifs e whiles vazios, código duplicado.
FindBugs é uma outra ferramenta de análise de código que visa encontrar problemas na implementação, como acesso de índices fora de um array, objetos não utilizados, código fora de padrão.
Este tipo de análise contribui para a qualidade do código desenvolvido por um time, ajudando na padronização do código desenvolvido. Estas ferramentas também podem calcular quanto tempo será gasto para tornar arrumar código do software analisado, como acontece com o Sonar, que é uma plataforma que une diversas destas ferramentas afim de facilitar a delas no processo de desenvolvimento. O sonar cobre desde arquitetura, estilo de código, quanto bugs, possíveis bugs e comentários.

Cobertura de código

Um outro tipo de ferramenta bastante utilizado junto com os testes, são as ferramentas de cobertura de código.
Como exemplo temos o emma, o coverage, o cobertura e o JaCoCo. Estas ferramentas marcam quais partes do código foram testadas, fazendo com que fique fácil vizualizar o que não está sendo testado e assim contruir um teste para passar por aquela parte.
Mas devemos ter 100% de cobertura de código, ou seja, os nossos testes devem passar obrigatoriamente por todo o código desenvolvido? Isso garante que o software será livre de bugs?
Não, nenhum, nem outro, quanto maior for a cobertura de código por testes, mais tempo será gasto no desenvolvimento deles. Não há um número exato de cobertura que garanta que o codigo está bom, e nem devemos perseguir este número.
Estas ferramentas não garantem que todas as possibilidades do software foram testados, elas só mostram para o desenvolvedor qual parte do código ainda não foi testada. A grande vantagem destas ferramentas é apontar ao desenvolvedor quando uma parte do código, que deveria ter sido testada, ainda não foi. Assim fica fácil vizualizar e construir um teste para cobrir o que deveria ter sido testado mas não foi.

segunda-feira, 19 de outubro de 2015

Evitando exceções ao gravar com JPA e Hibernate - um guia prático

Muitos usuários tem problemas ao gravar dados de múltiplas tabelas, neste post serão explicados como evitar este tipo de problema.

Exceção de Detached Object

Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: br.unip.dsd.modelos.Usuario
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141) 
Este é um erro muito comum de acontecer com objetos java. Esta exception ocorre quando tentamos gravar um objeto que já está no banco, ou seja tem um id definido (no caso dos objetos em que o id for gerado pelo banco). Mas estamos tentando gravar ele em uma outra conexão, por exemplo você buscou o objeto no banco de dados, fechou a conexão ou transferiu ele para um outro bean, como estamos fazendo no caso da gravação do usuário aqui, usando este código:
<h:commandButton value="Continuar"
 action="#{usuarioDetalheBean.registrar(usuarioBean.usuario)}" class="btn btn-default btn-lg"
        ajax="false">
  <f:param name="includeInSalvar" value="true" />
 </h:commandButton> 
Repare que neste código  que o valor da entidade usuario, é repassada do usuarioBean para o usuarioDetalheBean. Com isto este objeto ficará desligado do banco, uma vez que o objeto que inseriu ele no banco, o usuarioBean, é diferente do objeto que irá gravá-lo novamente. Lembre-se que ao gravar um objeto relacionado com o usuário, no caso o usuário bean, o hibernate irá tentar gravar também o objeto usuário, já que este objetos são relacionados pela chave primária:
        @OneToOne
 @PrimaryKeyJoinColumn
 @MapsId
 private Usuario usuario;
Esta exceção é um tipo de proteção do hibernate, pois ele não sabe há quanto tempo este objeto foi pego do banco, e muito menos se houve alguma alteração, por isso ele dispara esta exception.
Para resolver este problema, vamos reatachar este objeto ao banco, para fazer isto muitas pessoas recomendam ou atualizar, ou fazer um merge deste objeto. Estes métodos fazem uma operação de atualização no banco de dados, mas como no nosso caso este objeto não deve ter sido alterado, então vamos buscá-lo novamente no banco, assim:

  Usuario usuarioBanco = repositorioUsuario.findOne(usuario.getId());
  usuarioDetalhe.setUsuario(usuarioBanco);
Desta forma, ao gravarmos o objeto usuário detalhe, o hibernate não irá disprar nenhuma exceção.

TransientPropertyException ao salvar relacionamentos

Uma outra exception bem comum de acontecer é a de TransientException, como mostrado abaixo:
org.hibernate.TransientPropertyValueException: object references an unsaved 
transient instance - save the transient instance before flushing
Esta exception acontece quando tentamos salvar um dado de algum relacionamento, geralemente onetomany ou manytoone, e o dado filho não está gravado.
Por exemplo, em nossa estrutura mostrada nesta figura ao salvarmos o UsuarioDetalhe, sem antes termos salvo os dados do Endereço, esta exception será disparada.
E como resolver este problema?
Existem duas formas, a primeira é alterar o tipo de cascada do relacionamento:

@Entity
public class UsuarioDetalhe {

 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @OneToOne(cascade=CascadeType.ALL)
 @PrimaryKeyJoinColumn
 private Endereco endereco;
Veja que ao setarmos o tipo de relacionamento para cascade=CascadeType.ALL todas as operações que forem realizadas no objeto pai, neste caso o UsuarioDetalhe, serão também realizadas no objeto filho. Portanto ao salvarmos o objeto UsuarioDetalhe, o objeto Endereco também será salvo.
Existem algumas ressalvas a este tipo de solução:
Este tipo de cascata é bem perigoso, pois ao configuar isso em todos os seus relacionamentos, um simples delete em uma tabela de alta hierarquia, com muitos relacionamentos filhos, podem apagar muitos dados do seu banco de dados. É bem fácil de perder o controle com este tipo de configuração.
A outra forma de evitarmos esta exception é:
@Transactional()
public void gravaDetalhe(Usuario usuario,UsuarioDetalhe usuarioDetalhe, Endereco endereco, Rua rua, Cidade cidade, 
  Estado estado, TipoLogradouro tipoLogradouro, CEP cep) {
 Usuario usuarioBanco = repositorioUsuario.findOne(usuario.getId());
 usuarioDetalhe.setUsuario(usuarioBanco);
 repositorioCidade.save(cidade);
 cep.setCidade(cidade);
 repositorioRua.save(rua);
 cep.setRua(rua);
 repositorioEstado.save(estado);
 cep.setEstado(estado);
 repositorioTipoLogradouro.save(tipoLogradouro);
 cep.setTipoLogradouro(tipoLogradouro);
 repositorioCEP.save(cep);
 endereco.setCep(cep);
 repositorioEndereco.save(endereco);
 usuarioDetalhe.setEndereco(endereco);
 repositorioUsuarioDetalhe.save(usuarioDetalhe);  
}
Aqui neste método todos os objetos são salvos em uma sequência lógica, obedecendo a ordem, primeiro salva-se os filhos, para depois salvar os objetos com hierarquia mais alta:
Usuário -> Endereço ->  Cidade, Estado, Rua e Tipo de Logradouro -> CEP
Com isto, todos os objetos já estarão salvos ao salvarmos o UsuarioDetalhe, que é o objeto pai de toda esta hierarquia.
Todas estas operações estarão dentro de uma mesma transação, garantidos pela anotação @Transaction, conforme mostrado aqui.

Exemplo de cadastro de Usuários com o JSF

Como fazer um cadastro de usuários com o JSF?
Seguindo na sequência de como desenvolver um aplicativo web completo com Java e JSF
Todo o código referente a estes posts estão no github.

Estrutura do banco

Como dito anteriormente, o primeiro passo para se construir um sistema é a definição da estrutura do seu banco de dados, a estrutura escolhida para este passo do sistema está na Figura 1.
Figura1: Estrutura do banco de dados
A tabela de usuário só possui o nome do usuário e o email dele, note que neste caso o sistema só permite um email por usuário, se o seu sistema necessita de mais de um email por usuário, você deverá criar uma tabela específica para os emails do usuário.
Foi criada uma tabela separada para a senha do usuário afim de garantir uma maior segurança dos dados. Como a senha está isolada em uma outra tabela, ela só será pesquisada pelo sistema quando o usuário logar, ou quando houver alguma alteração da senha.
Caso a senha estivesse na mesma tabela do usuário, os dados da senha ficariam trafegando pelo aplicativo toda hora em que os dados do usuário fossem requisitados, o que aumenta a possibilidade de alguém coletar esta informação, e mesmo que criptografada, pode ser que esta senha seja descoberta.
Na parte do endereço do usuário, houve uma normalização dos dados de cidades, estados e ruas. Note que nem sempre esta normalização é necessária, poderíamos ter uma tabela de endereço com todos estes dados, mas a normalização deles nos traz a vantagem de não termos que repetir o cadastro de dados que já existam.
Todas estas tabelas estão ligadas a tabela CEP, para que com isso seja possível localizarmos estes dados pelo CEP, assim que o usuário digitar um CEP que já existe no sistema ele deve trazer os valores daqueles campos, facilitando o cadastro das informações do endereço.
Porém isto também levanta um alerta, e se o usuário estiver cadastrando um CEP e aquele valor já existir para uma certa Rua de um município? Bom alguns municípios pequenos possuem apenas um CEP, com isso todas as ruas deles estão em um mesmo valor do CEP, assim podemos ter dados de CEPs que só possuem o estado e a cidade.
Para tratarmos estes casos, devemos ter uma certa lógica de negócios ao utilizarmos o cadastro do CEP. Uma das atitudes que podem ser tomadas é nunca cadastrar todos os dados do CEP, quando ele tiver sendo inserido pela primeira vez em uma cidade. Se uma cidade nunca foi cadastrada no sistema, o CEP só vai conter os valores do estado e da Cidade, e quando houver um segundo cadastro, com a mesma rua, ai sim completa-se a informação do CEP. Mas fazer isso durante o cadastro do endereço do usuário não é uma boa opção, já que o sistema deve fazer algumas queries para realizar estas operações, entõa o ideal é deixar isto para um sistema de batch que rode de tempos em tempos.
E o que fazer quando duas ruas foram cadastradas para um CEP de uma cidade, mas esta cidade não possui CEP único? Neste caso, você pode tentar tratar pela maioria, ou mesmo fazer uma análise manual, isto vai depender da quantidade de dados que a sua base tem neste caso.

Validações e regras de negócio

Existem algumas validações que podemos e devemos fazer durante este cadastro, por exemplo se a senha possui no mínimo 6 caracteres e 2 dígitos. Para fazer este tipo de validação iremos usar uma classe validadora do JSF, para isso deve-se adicionar a tag validator no seu componente, como mostrado a seguir em destaque:
<h:inputSecret id="senha"
                        value="#{usuarioBean.usuarioSenha.password}"
                        validator="validadorDeSenha"
                        required="#{not empty param.includeInSalvar}"
                        pt:placeholder="Qual é a sua senha?" />
O código de validação será implementado em um bean que implementa a interface Validator do faces. O único método que esta interface define é o validate, como mostrado abaixo:
public void validate(FacesContext context, UIComponent iuComponent, Object valor)
            throws ValidatorException {
        if(!padraoSenha.matcher(valor.toString()).matches()){
            MessageFactory msg = new MessageFactory();
             throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
                            msg.getMessage("senhaInvalida"), null));             
        }
    }
FacesContext é o contexto faces e podemos usar ele para, por exemplo redirecionar o usuário para uma outra página de erro. O componente é o objeto referente ao componente da tela que está sendo validado e finalmente o Objeto será o valor digitado pelo usuário.
Neste caso estamos apenas validando o valor digitado, usando REGEX (regular expression), nossa REGEX de validação de senha verifica se os valores digitados estão entre 6 e 20 caracteres de tamanho, e se ela contém pelo menos 2 letras e pelo menos 2 números, abaixo segue a implementação do REGEX:
((?=.{2,}\\d)(?=.{2,}[a-zA-Z]).{6,20})
(
   (?=.{2,}\\d) verifica se contém pelo menos 2 ({2,}) dígitos \\d
   (?=.{2,}[a-zA-Z]) verifica se contém pelo menos 2 ({2,})   letras(a-zA-Z) de a a z minúsculo e A a Z maiúsculo
   .{6,20} verificacao de tamanho;
)
Repare que para o regex a definição de números é dada por \d mas como \ no java é um caracter especial, devemos utilizar o caracter de escape que também é uma \, por isso temos \\d.
Para utilizar o regex em java usa-se a classe chamada Pattern:
private Pattern padraoSenha=  Pattern.compile("((?=.{2,}\\d)(?=.{2,}[a-zA-Z]).{6,20})");
Regex é um tema muito amplo que não cabe ao escopo deste post, por isso não vamos discutí-lo aqui.package br.unip.dsd.bean.validador;
Basicamente a classe validadora segue os mesmos princípios do ManagedBean e para definirmos ele, usamos a anotação @FacesValidator sobre a classe. Com isso ele poderá ser acessado nas suas páginas usando o nome da classe, no mesmo padrão dos beans, primeira letra minúscula e demais de acordo com o nome da classe.
Quando o usuário digitar uma senha que não atende aos padrões do nosso regex, uma mensagem de erro será mostrada para ele ao lado do componente.
Porém esta ação de validação acontece nas ações da tela, por exemplo, quando o usuário clicar no botão de salvar.
Quando a validação falha uma exceção do tipo ValidatorException é disparada, e este é o comportamento padrão dos validadores. Repare que nada deverá ser feito se o valor digitado pelo usuário estiver correto.
Lembre-se que cada pacote de beans adicionado ao projeto, deverá ser adicionado a propriedade basePackages, da anotação @ComponentScan, na classe de configuração, que no nosso caso é a JPAConfig.

Usando enums internacionalizados para os dados tipados

É comum o uso de enumerations para dados tipados, que raramente terão os valores alterados. Os enums basicamente são uma lista de valores introduzidos no código e que são gravados em um campo. Para gravarmos os valores dos enums em um campo existem duas possibilidades:
  1. usar um id no enum, valor numérico
  2. usar o toString do enum
Abaixo segue um exemplo de como usar um enum no modo string:
        public List<SelectItem> getEstadoCivil() {
           List<SelectItem> estadoCivil = new ArrayList<SelectItem>();
           MessageFactory msg = new MessageFactory();
       
           estadoCivil.add(new SelectItem(EstadoCivil.CASADO.toString(), msg.getMessage("casado")));   
           estadoCivil.add(new SelectItem( EstadoCivil.SOLTEIRO.toString(), msg.getMessage("solteiro")));
           estadoCivil.add(new SelectItem(EstadoCivil.DIVORCIADO.toString(), msg.getMessage("divorciado")));
           estadoCivil.add(new SelectItem(EstadoCivil.VIUVO.toString(), msg.getMessage("viuvo")));
           return estadoCivil;
    }
O código é bem simples, o primeiro ponto a observar é a utilização de um MessageFactory, que está sendo usado aqui para a internacionalização dos valores do enumeration.
SelectItem é um objeto utilizado para mostrarmos dados como uma lista, em uma combobox, para que o usuário selecione o valor desejado.
Através do SelectItem são enviados dois valores de String para a tela, primeiro o valor do enum internacionalizado, que irá aparecer na lista para o usuário. Depois o valor do enum transformado em String, que será usado para gravarmos no banco.

Amarrando os dados 

No próximo post será explicado como evitar exceções ao usar o jpa e hibernate.

segunda-feira, 12 de outubro de 2015

Camada de Serviço em um aplicativo Web com Java e Spring

Continuando com os posts de desenvolvimento de um sistema web, aqui será descrita a utilização da camada de serviços.
Para um sistema simples as regras de negócio podem ser colocadas no controller, mas quando o sistema começa a ficar um pouco mais complexo, o ideal é criarmos uma camada de serviço em nossa aplicação.
Esta camada nada mais é do que um local onde ficarão concentradas as regras de negócio do sistema.
Validações simples dos dados, como o tamanho de um campo (senha com no mínimo 6 caracteres, etc) são feitas no controller. Já validações como, este usuário já está inserido no banco?, transações e alterações em múltiplas tabelas do banco de dados, verificações específicas de um sistema, como validação de o aluno tirou nota mínima para ser aprovado ou não. Este tipo de regra fica na camada de serviço.
Em nosso sistema a demarcação desta camada é feita pela anotação @Service. Esta anotação define um bean da camada de serviço.
Toda parte do sistema que tiver uma regra mais complexa, como a inserção de usuários, que inserem valores em duas tabelas, são feitas nesta camada.
No sistema desenvolvido aqui, esta camada ficará nesta parte do repositório.

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)