pedrocacique

Torneio Marvel - Parte 2 - Persistência

28 de março de 2016

Olá pessoas!

Agora que já temos o nosso projeto configurado podemos continuar o nosso Torneio Marvel com as classes de entidade para a persistência de dados.

Se você perdeu a primeira parte, clique AQUI para aprender a configurar o seu projeto.

Não sei você, mas eu me concentro muito mais na programação ouvindo uma boa música. A playlist de hoje é a Jukebox Burger, do Spotify. Coloque seus fones de ouvido (menos se você estiver em aula, neste caso, feche o navegador e presta atenção na aula!) e vamos programar.

Neste sistema, vamos criar uma espécie de Super Trunfo de super heróis. Buscaremos, nos bancos de dados da Marvel (veremos mais a frente como acessar a sua API), as informações de cada personagem. Portanto não precisamos armazenar nenhum dado de nenhum herói. Focaremos então nas informações de cada usuário e de suas partidas, para criarmos um ranking dos melhores jogadores.

Antes de começarmos, atenção para os direitos de uso e cópia do projeto:

Creative Commons License
Torneio Marvel by Pedro Cacique is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.

E ainda:

"Data provided by Marvel. © 2014 Marvel"

www.marvel.com

Vamos trabalhar a princípio no módulo EJB do projeto principal. Usaremos o pacote entities, conforme a imagem a seguir:

Quando usamos a Java Persistence API (JPA) para trabalhar com persistência, podemos criar primeiro as tabelas do banco de dados e depois as classes em Java que as representem, ou ainda podemos criar as classes com as devidas anotações, permitindo ao projeto criar o BD com as especificações estabelecidas.

Para este projeto, vamos criar as classes primeiro, mas se você quiser, pode seguir o segundo método. Criaremos as entidades com os dados relevantes para esta aplicação, assim guardaremos apenas os dados essenciais de um usuário. Lembre-se que todo projeto deve começar com uma etapa de planejamento, para identificar todos os requisitos funcionais e não funcionais do sistema. Não deixe para planejar enquanto programa, pois você terá trabalho dobrado!

Precisamos armazenar os seguintes dados de um usuário:

  • Nome de usuário
  • Senha de acesso
  • Nome completo
  • Email
  • Data de nascimento
  • Pontuação no jogo

Poderíamos ter muitas outras informações, mas para este sistema, estes são os dados relevantes.

Afim de melhorar a perfomance do sistema, vamos separar os dados do usuário em duas tabelas: uma contendo as informações de acesso apenas (usuário e senha) e outra com as demais informações. Assim, podemos diminuir o tempo de busca durante o acesso ao site. Imagine uma busca no nosso sistema quando tivermos 10 usuários. Provavelmente será bem rápida. mas e quando tivermos 1 milhão? A busca se comportará da mesma forma?

Para a partida, guardaremos:

  • ID do usuário
  • Data e hora da partida
  • ID do herói escolhido pelo jogador
  • ID do herói escolhido pelo oponente(máquina)
  • Status da partida (se ganhou ou perdeu)

Observe que teremos então três classes de entidade: Usuário (MarvelUser), Informações do Usuário(UserInfo) e Partida (MarvelMatch). Podemos, agora, pensar no relacionamento entre elas. Cada usuário contém apenas um conjunto de informações, o que nos dá um relacionamento 1 para 1. Já entre o usuário e a partida, temos um relacionamento de um para muitos, uma vez que cada um pode jogar inúmeras partidas.

Para programar as Classes de Entidade do nosso sistema, vamos começar pelas informações do usuário, que constituem a tabela mais simples do banco de dados e, por consequência, a classe mais simples. Começarei por ela, por ser uma das tabelas sem nenhuma chave estrangeira no banco.

Observe que, como estamos tratando uma entidade como a representação de uma tabela no banco de dados, é comum pensaramos na classe como uma tabela, mas lembre-se que são duas coisas distintas, conectadas pela unidade de persistência (falaremos dela mais tarde).

UserInfo

Veja a minha implementação da classe UserInfo.java:

Observe que é uma classe simples que contém apenas os atributos desejados (descritos acima), um construtor vazio, os métodos GETTERs e SETTERs e o método toString(), para podermos visualizar os dados mais adiante.

Observe que estamos implementando a interface Serializable, para que um objeto desta classe possa ser serializado corretamente.

Você deve ter percebido também algumas anotações, escritas com o símbolo @. Estas anotações são feitas para estabelecer os atributos persistentes no banco de dados. Se queremos que esta classe seja uma entidade persistente, devemos colocar a anotação @Entity imediatamente antes da declaração da classe.

Como toda tabela de banco de dados relacional possui uma chave primária (PK) para identificação dos registros, criamos o atributo id_userinfo, que foi anotado com @Id, para que seja a PK e em seguida a anotação @GeneratedValue, dizendo que este atributo é uma identidade, isto é, não permite que haja valores duplicados no banco. Quando trabalhamos com SQL, dizemos que é uma primary key, provavelmente como not null e auto_increment.

Por fim, todos os atributos que se referem a datas e horários devem ser marcados com a anotação @Temporal, para que a persistência possa fazer as conversões necessárias entre os tipos de dados do java e do sql. Por exemplo, definimos o TemporalType.DATE para dizer que o elemento birthday (do tipo java.util.Date) deve ser convertido para java.sqj.Data.

MarvelMatch

Podemos agora fazer algo análogo para a classe Match, com os dados da partida.

Veja como fica a minha implementação:

Observe as anotações! Para este caso, colocamos as anotaçõs @Entity, @Id e @GeneratedValue da mesma maneira que usamos anteriormente. Agora, a anotação @Temporal recebe como atributo o tipo TemporalType.TIMESTAMP, pois guardaremos neste atributo a data e a hora da partida.

Por fim, usamos a anotação @ManyToOne, pois se um usuário pode ter várias partidas, teremos um relacionamento de um para muitos no sentido User -> Match. Assim, consideraremos na classe User, uma lista de partidas para garantir o relacionamento. Por enquanto, não criamos a classe User.java, mas já estamos associando um objeto deste tipo para mais tarde (assi que criarmos a classe, o erro desaparecerá).

A documentação das anotações, no site da Oracle (veja aqui) apresenta duas formas de representar este relacionamento. A primeira é a UNIDIRECIONAL, na qual apenas uma das classes mantém o link para a outra. A segunda é a BIDIRECIONAL, na qual as duas classes apresentam o relacionamento (uma com um objeto e outra com uma lista de objetos). Usaremos o modelo bidirecional.

A anotação @ManyToOne deve ser seguida da anotação @JoinColumn, que recebe o nome do atributo na classe usuário que será usado para a ligação entre elas.

MarvelUser

Por fim, vamos criar a entidade User. Veja:

Além das anotações que já falamos, vamos colocar aqui a anotação @OneToOne, que estabelecerá um relacionamento de 1 para 1. Observe que atribuimos a ela a propriedade cascade para que atualize por cascata o registro na tabela UserInfo.

Colocamos a anotação @PrimaryKeyJoinColumn com os atributos name (para indicar qual atributo na tabela relacionada será usado como chave estrangeira) e referencedColumnName (para indicar a chave primária da outra tabela).

Conexão com o Banco de Dados

Você deve ter notado (se está no Netbeans) que surgiram alguns alertas quando colocamos as anotações de persistência dizendo: Este projeto não contém uma Unidade de Persistência. Isos porque ainda não vinculamos propriamente as entidades com o banco.

Já criamos a conexão com o banco no post anterior, mas não vinculamos o nosso projeto com esta conexão. Para isso, precisamos criar uma Unidade de Persistência (PU). Para isso, clique com o botão direito sobre a pasta de código-fonte e selecione Novo > Unidade de Persistência. Caso Não apareça direto, selecione Outros e em seguida busque por Persistência, na lateral esquerda e logo por Unidade de Persistência no lado direito.

Selecione a base de dados configurada no post anterior e crie um nome para esta unidade de persistência. Por padrão, o nome é o mesmo do projeto. No nosso caso, apenas removi o sufixo "-ejb".

Para a estratégia de geração de tabelas, marcamos criar pelo menos para a primeira execução, depois podemos mudar para a estratégia "Nenhum", para que as tabelas não sejam recriadas.

Um arquivo chamado persistence.xml é criado na pasta Arquivos de Configuração do seu projeto EJB. Caso precise, volte a ele para consultar/alterar as informações de persistência.

Em JPA, precisamo estabelecer um gerenciador de entidades (EntityManager - EM) que será único durante a execução do projeto. Uma forma de garantirmos esta qualidade é colocá-lo em um Singleton. Este será chamado a cada uso da persistência.

Observe o código a seguir:

Temos aqui uma implementação simples do Design Pattern Singleton, que garante que apenas uma instância desta classe será criada.

Nas linhas 22 a 25 temos o construtor privado, que instanciará este objeto, criando o EntityManager. Veja que para isso precisamos de uma fábrica de gerenciadores de entidades. Esta é criada pela classe Persistence, com o nome da PU que criamos há pouco. Em seguida, esta fábrica cria o EntityManager, que estará disponível a partir de então.

Neste singleton criamos um método GETTER para o EM e um método de fechamento do mesmo que será chamado uma única vez no programa.

Como vamos usar as transações java, aproveito para abrir uma transação no construtor e enviá-la para execução no método commitTransaction().

Data Access Object (DAO)

Outro design pattern bastante usado quando estamos criando uma conexão com o banco de dados é o Data Access Object (DAO). Este padrão é elaborado para criarmos os métodos principais de modificação em um banco de dados. Costumamos chamar estes métodos de CRUD, um acrônimo para Create, Read, Update e Delete.

Alguns programadores acham desnecessária a criação do DAO quando usamos JPA, entretanto, ele é feito para garantir algumas características:

  • Os métodos são chamados por uma interface independente do Sistema Gerenciador de Banco de Dados (SGBD);
  • Separação da camada de dados da camada de negócios;
  • Temos uma interface comum de chamada dos métodos para qualquer entidade.

Ainda que o JPA simplifique o DAO (quando comparado ao JDBC puro), é interessante implementarmos esse padrão.

GenericDAO<E>

Para que todos as classes de DAO tenham o mesmo padrão, criaremos uma interface genérica, assim, todas as implementações terão ao menos os métodos de CRUD que dissemos anteriormente. Veja:

Estamos, aqui, trabalhando com uma interface que faz uso dos Generics do java, isto é, podemos criar um elemento na interface que será substituído, na implementação da classe, por uma classe real do sistema. Neste caso, declaramos o <E> como uma classe genérica.

Apesar de termos visto 4 métodos do CRUD, quando trabalhamos com JPA, os métodos de inserção e atualização de entidades é o mesmo [persist()]. Por isso, optei por deixar apenas o método persist(). Coloquei então os seguintes métodos:

  • persist(E e) (CREATE / UPDATE)
  • find() (READ - leitura de todos os registros da tabela)
  • findById(long id) (READ - leitura de um registro específico)
  • remove(E e) (DELETE - remoção do registro)

Observe ainda que estamos criando uma implementação do DAO como um EJB, assim, coloquei a interface @Local, para indicar que esta é uma interface local do nosso sistema.

MarvelUser

Vamos à implementação do DAO da classe MarvelUser

Começamos aqui pelo uso da anotação @Stateful, que foi colocada para indicar que este arquivo é um bean de sessão do tipo Stateful, ou seja, que guarda seu estado durante a sessão. Em um outro post do blog entraremos em detalhes sobre os tipos de EJBs.

Observe que implementamos a interface GenericDAO, substituindo o elemento genérico E pela classe MarvelUser, que é a principal entidade deste DAO.

Para que possamos trabalhar com as entidades, criamos um gerenciador de entidades, EntityManager. Como estamos usando-o em um EJB, podemos usar a anotação @PersistenceContext, que recebe o nome da PU e o tipo de contexto de persistência adotado. Como optamos por JTA anteriormente, usamos o tipo TRANSACTION aqui.

Caso não esteja usando o DAO em um EJB, é necessário criar o EntityManager e gerenciar as suas transações manualmente. Veja um exemplo de uma função que assim o faria:

Para isso, meu conselho seria a criação de um Singleton para abrigar o EntityManager e fazer o gerenciamento das transações.

Uma vez que estamos em um EJB e usamos a anotação @PersistenceContext, o container EJB se encarrega de gerenciar as transações por nós, simplificando o código do nosso DAO.

Para cada um dos métodos, chamamos os métodos específicos de JPA, conforme a imagem a seguir:

Estes são os estados de uma entidade JPA e os métodos que agem na sua transição.

Pronto! O DAO para a classe MarvelUser está completo e as implementações para as demais classes são análogas e por isso não as apresentarei aqui. Explicarei o uso do DAO em um próximo post, quando começarmos a trabalhar com o módulo war do projeto.

Por hoje é só!