quarta-feira, 19 de março de 2014

Persistência de objetos usando JPA - Prática

JPA na prática

Chega de teoria e partir agora vamos mostrar um objeto simples do tipo Produto, com um identificador único(id), nome e preço(preco) e como realizar operações de banco de dados com o mesmo usando JPA. Sim, um simples objeto, sem relacionamentos, sem atributos complexos, hoje vamos focar no essencial.
Mas antes de começar a programar, temos que configurar um projeto Maven. A primeira coisa é criar um diretório para colocarmos nosso projeto, eu chamei o meu de introducao-jpa(esse é o diretório base de todos os diretórios que vamos citar daqui em diante). Nesse diretório, crie um arquivo pom.xml  com o conteúdo que você pode encontrar aqui no github. Em seguida, crie a seguinte estrutura de diretórios:


* invés do diretório "resource", use "resources"
A partir desse momento, configuramos tudo que deveríamos. Veja o uso do plugin do JBoss que já abordamos aqui e a configuração de dependências, semelhante a dos quickstarts da JBoss.
Com o projeto preparado, vamos criar a classe do produto em src/main/java/org/jugvale/introducao/jpa/model/Produto.java com o seguinte conteúdo:

package org.jugvale.introducao.jpa.model;

import javax.persistence.*;
import javax.xml.bind.annotation.XmlRootElement;

@Entity
@XmlRootElement
public class Produto{
    
        @Id 
        @GeneratedValue
        private long id; 
        private String nome;
        private float preco;

        public void setId(long id){ this.id = id;}
        public void setNome(String nome){this.nome = nome;}
        public void setPreco(float preco){this.preco = preco;}

        public long getId(){return this.id;}
        public String getNome(){return this.nome;}
        public float getPreco() {return this.preco;}
}


Esse é nosso objeto, um POJO. O mapeamento entidade relacional se faz sobre uma classe dessa. É nela onde colocamos as anotações Java para definir qual a relação do nosso objeto com o banco de dados, ou seja, damos as meta informações para que o JPA possa trazer dados do banco e criar instâncias desse objeto. As anotações estão no pacote javax.persistence. Veja abaixo uma explicação básica para cada uma delas:
  • Entity: Identifica a classe POJO como uma entidade a ser persistida. 
  • Id: Para o JPA gerenciar as classes, temos que indicar para ele qual é o campo que identifica unicamente aquele objeto no banco de dados. Com essa anotação, o JPA saberá identificar cada instância do objeto unicamente como se fosse uma linha da correspondente tabela no banco de dados. O valor desse campo deverá ser único para cada instância do objeto
  • GeneratedValue: Aqui falamos para o JPA que não queremos gerar o ID, assim ele sabe que deverá colocar um valor aí para gente quando criarmos novas instâncias desse objeto e persistimos no banco de dados. Notem que podemos também definir a estratégia para geração do valor desse ID.
Pronto. Já temos nosso objeto mapeado para persistência em um banco de dados. Mas que banco de dados? Temos agora que adicionar dois arquivos de configuração à nossa aplicação:

1) introducao-jpa-ds.xml - Datasource onde descrevemos para o JBoss as informações de conexão com o banco de dados. O conteúdo do nosso Datasource pode ser visto aqui e o mesmo deve ser colocado na pasta src/main/webapp/WEB-INF do nosso projeto. No momento estamos usando o h2 para persistir. O jboss já vem com esse banco de dados configurado, que não é preparado para produção, mas serve perfeitamente para nosso artigo.
2) persistence.xml - Arquivo de configuração do JPA para setarmos alguns parâmetros do JPA e hibernate. O conteúdo do arquivo também está no github e deve ser colocado no diretório src/main/resource/META-INF. Notem que o persistence.xml faz referência ao Datasource no campo jta-data-source e o parâmetro hibernate.hbm2ddl.auto configurado como create-drop, isso quer dizer que os dados vão ser limpos toda vez que fizermos o redeploy da aplicação.

Após essas duas modificações, temos a seguinte estrutura de diretórios no nosso projeto:

* invés do diretório "resource", use "resources"
Maravilha! Já temos uma aplicação apta a acessar um banco de dados. Mas como vamos acessar o banco de dados? Com JPA, temos uma classe chamada EntityManager que é responsável por realizar operações com o banco de dados. Podemos usar o EntityManager em um servlet, um Web Service do tipo JAX-WS e JAX-RS, já abordados no nosso blog. Hoje vamos ser simplicistas e vamos injetar o EntityManager em um recurso JAX-RS e expor as operações de banco de dados através de uma interface REST.

Expondo as operações de banco de dados usando uma interface REST

Digamos que queremos aplicar as 4 operações básicas na nossa classe Produto: criar, apagar e buscar. Vamos expor essas operações usando uma interface HTTP que segue os princípios REST, em outras palavras, vamos traduzir para código o seguinte:

"Os produtos devem ser acessados por uma URL. Cada produto por seu ID. Um GET na URL dos produtos, deverá retornar todos os produtos, um POST deverá criar.  Um DELETE deverá apagar o produto. Tipo de dado para troca de informações será JSON."

Nada mais perfeito para atingir isso do que JAX-RS. Veja o código da nossa classe ProdutoEndpoint. Esse código deve estar no arquivo src/main/java/org/jugvale/introducao/jpa/rest/ProdutoEndpoint.java 
package org.jugvale.introducao.jpa.rest;

import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.Status;
import javax.persistence.*;
import java.util.List;
import org.jugvale.introducao.jpa.model.Produto;
import javax.ejb.Stateless;

@Path("produto")
@Produces("application/json")
@Stateless
public class ProdutoEndpoint{

 @PersistenceContext(unitName = "primary")
 EntityManager em;

 @GET
 public List buscarTodos(){
  return em.createQuery("SELECT p FROM Produto p").getResultList();
 }

 @POST
 @Consumes("application/json")
 public Response criar(Produto p){
  em.persist(p);
  return Response.created(UriBuilder.fromResource(ProdutoEndpoint.class)
      .path(String.valueOf(p.getId())).build()).build();
 }

 @GET
 @Path("{id}")
 public Produto recuperaPorId(@PathParam("id") long id){
  Produto p =  em.find(Produto.class, id);
   if(p == null)naoEncontrado(id);
   return p;
 }

 @DELETE
 @Path("{id}")
 public void apagar(@PathParam("id") long id){
  Produto p = recuperaPorId(id);
  em.remove(p);
 }
 private void naoEncontrado(long id){
  throw new WebApplicationException(Response.status(Status.NOT_FOUND)
      .entity("Produto com ID '"+ id + "' não encontrado").build());
 }
}

Para mais informações  sobre JAX-RS e os métodos que usamos aí, veja o material que temos sobre JAX-RS.

Observe que temos sobre a nossa classe uma anotaçãozinha mágica chamada @Stateless. Com ela, o JBoss gerencia a classe pra gente e quando fazemos operações com o nosso EntityManager , o JBoss cuida de gerenciar as transações! O EntityManager  é injetado de acordo com os dados do contexto de persistência, por isso a anotaçãozinha @PersistenceContext em cima dele. Assim não precisamos instanciar, nem preocupar se o objeto fica em memória, JBoss faz tudo! Mágico, não?
Falando em transações, aqui vai uma explicação básica dos métodos do EntityManager que usamos no nosso Endpoint:

  • persist: Persiste o objeto no banco de dados. Após chamar esse método e commitar a transação, o objeto passa a estar disponível no banco de dados;
  • remove: Irá apagar o objeto do banco de dados;
  • find: Encontra um objeto do tipo passado através do seu ID;
  • createQuery: Envia uma consulta ao banco e retorna uma lista de objetos resultado daquela consulta

Agora só precisamos falar pro JBoss qual a URL no servidor que nossos serviços REST vão ficar disponíveis. Para isso, usamos o famigerado Activator, como abaixo, e colocamos os produtos sob a URL "/rest":

package org.jugvale.introducao.jpa.rest;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("rest")
public class JaxRSActivator extends Application {

}

Parabéns, você já terminou sua aplicação CRUD! Mas como testar?

Inserindo dados de teste

 Para ter certeza que está tudo certo, vamos inserir uns dados de brincadeira e acessar nosso Endpoint usando um navegador. Para inserir esses dados, criamos uma classe que terá o método executado só uma vez. Futuramente teremos um artigo dedicado a falar dos EJBs (classes gerenciadas pelo JBoss) mais famosos. Por agora, simplesmente adicione o código abaixo no arquivo src/main/java/org/jugvale/inicio/AdicionaProdutos.java. Ele usa o EntityManager para criar alguns produtos.

package org.jugvale.introducao.jpa.inicio;

import java.util.Date;

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.jugvale.introducao.jpa.model.Produto;

@Singleton
@Startup
public class CarregaDadosIniciais {
 
 @PersistenceContext(unitName = "primary")
 EntityManager em;


 @PostConstruct
 public void carregaDadosIniciais() {
  Produto p1 = new Produto();
  p1.setNome("Produto1"); p1.setPreco(200f);
  Produto p2 = new Produto();
  p2.setNome("Produto2"); p2.setPreco(150f);
  em.persist(p1);
  em.persist(p2);
 }
}



Build, Deploy, Ação

Como esse artigo se estendeu mais do que eu esperava, vamos falar de testes mais profundos e uma página WEB para acessar essa interface REST no próximo artigo. Para os mais apressadinhos que querem ver a coisa em ação, seguem as instruções:
  • Confira a sua aplicação: A nossa aplicação já está no github e segue aqui a estrutura final de diretórios até esse momento:

  • Inicie o JBoss AS 7.1.1 ou EAP 6.x: Conforme já falamos aqui anteriormente, use o script ./bin/standalone.sh para iniciar o JBoss. Quando o JBoss "estiver de pé", você deverá ter acesso à página do servidor através de URL localhost:8080 no navegador;
  • Faça build e deploy: Tenha o Maven instalado, e na raiz do nosso projeto(onde está o arquivo pom.xml) execute o comando $ mvn clean package jboss-as:deploy
  • Acesse os produtos que inserimos pelo navegador: Os produtos que inserimos usando a classe AdicionaProdutos devem estar disponíveis na URL http://localhost:8080/introducao-jpa/rest/produto. Acessa a mesma usando seu navegador e você deve ter algo como o seguinte sendo retornado:

 Caso não tenha, deixe nos comentários o problema que enfrentou e vamos procurar uma solução.

O vídeo abaixo complementa esse artigo:



Conclusão

Mostramos o básico de JPA na prática! Não temos uma aplicação funcional, mas voltaremos em uma postagem futura com uma página bonitinha! Até lá.

Um comentário:

  1. Olá William, estou tentando criar uma aplicação bonitinha, rsrsrs. Um simples CRUD para cadastro com tabelas relacionadas no BD, estou enrolado na parte do CDI e não encontro nada que resolva, você pode dar uma ajuda?

    ResponderExcluir