terça-feira, 29 de janeiro de 2013

Usando JPA 2 e jstl no Tomcat 7

Olá,

Hoje vamos mostrar como subir uma pequena aplicação no apache tomcat 7 que faça uso de JPA pra facilitar as manipulações do banco de dados e jstl para facilitar a apresentação de dados no jsp.

Eis o material que usaremos:

  • Fedora 17 64 bits
  • OpenJDK 1.7
  • Apache Tomcat 7.0.27
  • eclipse indigo SR2 (3.7.2)
  • H2 database 1.3
  • EclipseLink 2.3.1

Como de costume, vamos proceder com os downloads. Para instalar o JDK, entre num terminal o seguinte comando:

sudo yum install java-1.7.0-openjdk-devel

Faça o download do tomcat direto da fonte, preferencialmente a versão .tar.gz:

O download do tomcat também providenciará o suporte a jstl, que veremos como ajustar mais à frente.

Faça o download do eclipse em sua versão JEE:

O banco de dados H2 você baixa a versão zip:

O EclipseLink você baixa o "Installer Zip":

Uma vez coletados os ingredientes, vamos proceder com a montagem do ambiente. Entre na pasta Downloads e descompacte os arquivos que baixamos:

Entre na pasta do eclipse e execute-o. Deixe-o com o workspace padrão:

Uma vez carregado, o eclipse lhe apresentará uma tela de welcome. Livre-se dela e siga para o workbench:

Aí está, o workbench JEE do eclipse, velho conhecido seu, assim espero. Siga para a aba Servers para criarmos um novo servidor clicando no link "new server wizard":

No campo "Select the server type" escreva "tomcat v7" e selecione a opção que sobrar:

Aperte Next, e você verá a tela de configuração da runtime de servidor. Aperte browse e selecione a pasta do tomcat que descompactamos previamente:

Após pressionar finnish, você terá o tomcat disponível no seu workspace:

O passo seguinte é muito simples, vamos criar um novo projeto web chamado agenda. pressione o atalho disponível na barra do eclipse:

Pronto, temos um projeto Java web disponível, podemos começar a trabalhar de verdade, :-)

Nosso modelo de dados, de maneira geral, terá Contatos e Endereços. Nada mais. O único detalhe relevante é que um Contato poderá ter um ou mais Endereços.

Assim sendo, teremos duas telas de cadastro e duas telas de listagem, além de duas telas de status intermediárias que explicarei a seguir.

Vamos começar com as classes de modelo. Crie uma classe chamada Endereco no pacote exemplo.model:

A classe Contato você faz seguindo os passos já mostrados acima. Vamos adicionar alguns atributos ao nosso modelo de dados, a começar pelo Endereço:

package exemplo.model;

public class Endereco {

	private long id;
	private String rua;
	private int numero;
	private String complemento;
	
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}
	public String getRua() {
		return rua;
	}
	public void setRua(String rua) {
		this.rua = rua;
	}
	public int getNumero() {
		return numero;
	}
	public void setNumero(int numero) {
		this.numero = numero;
	}
	public String getComplemento() {
		return complemento;
	}
	public void setComplemento(String complemento) {
		this.complemento = complemento;
	}
}

A classe Contato ficará assim:

package exemplo.model;

import java.util.List;

public class Contato {

	private long id;
	private String nome;
	private String telefone;
	private List enderecos;

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getNome() {
		return nome;
	}

	public void setNome(String nome) {
		this.nome = nome;
	}

	public String getTelefone() {
		return telefone;
	}

	public void setTelefone(String telefone) {
		this.telefone = telefone;
	}

	public List getEnderecos() {
		return enderecos;
	}
	
	public void setEnderecos(List enderecos) {
		this.enderecos = enderecos;
	}
}

Uma vez criado o modelo, hora de escrever telas que o representem. Vamos criar a tela de cadastro de Contato:

Este JSP é simples, teremos um formulário bem básico, nada complicado de entender:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>

Salvar Contato

</head>
<body>
	

</body> </html>

Você já pode até ver como ele fica no navegador, com ele aberto basta apertar o "play":

Atenção para o atributo action da tag form: ela tem o nome da ação que este formulário tentará executar. Se você apertou o botão Salvar e um erro 404 apareceu, não se preocupe, vamos corrigir isso agora. De volta ao eclipse, vamos criar um servlet:

Após apertar Next, um passo importante: mude o valor do URL Mappings para "/vaisalvar", que é o que tem no form. Só então pressione finnish:

O servlet basicamente mapeia os verbos HTTP para métodos java. Como não informamos o atributo method na tag form, o verbo aqui será o GET. Assim sendo, vamos modificar este servlet que aí está para que ele fique deste jeito:

package exemplo.controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import exemplo.model.Contato;

@WebServlet("/vaisalvar")
public class Salvar extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		long id = 0L;
		String sId = request.getParameter("id").trim();
		String sNome = request.getParameter("nome");
		String sTelefone = request.getParameter("telefone");

		if (!"".equals(sId))
			id = Long.parseLong(sId);

		Contato c = new Contato();
		c.setId(id);
		c.setNome(sNome);
		c.setTelefone(sTelefone);

		// TODO mais modificações a seguir

		// devemos ainda redirecionar para a tela de resultado
		request.getRequestDispatcher("/sucesso.jsp").forward(request, response);
	}

}

Já podemos ver parte do trabalho caminhando: temos a recuperação dos dados do form através de chamadas "request.getParameter", verificação de id válida (que será útil para salvar um novo ou atualizar), e redirecionamento para uma página intermediária de status. Página essa, que você deve criar no projeto, de forma parecida com o que foi feito na criação do jsp de contato.

Poderíamos fazer o fowrard para o mesmo jsp de origem, entretanto ao usar uma página intermediária você não precisará prever a limpeza do formulário após a submissão. É um problema antigo, típico de web 1.0, e que muitas pessoas esquecem como combater.

Neste momento, precisamos retornar às configurações para habilitarmos o suporte do H2 e do JPA neste projeto. Retorne à pasta Downloads e entre na pasta descompactada do H2; vamos copiar a biblioteca do banco para as bibliotecas do tomcat:

O h2 deverá então ser listado como uma das bibliotecas do tomcat dentro do projeto eclipse:

Como visto em tutoriais anteriores, com JEE a forma correta de configurar o acesso ao banco de dados é via DataSource. Este nós vamos configurar no contexto global do servidor.

Entre no projeto Servers, na pasta de configuração do tomcat 7 e abra o arquivo server.xml, procure a seção GlobalNamingResources:

Adicione nesta seção o seguinte DataSource:

	  

Deve ficar assim:

Agora siga para a pasta do projeto, vamos para WebContent>META-INF. Lá crie um arquivo chamado context.xml com o seguinte conteúdo:



	

Isso dá à aplicação o direito de usar o DataSource.

Caso fôssemos usar apenas JDBC, nosso trabalho de configuração de banco de dados estaria terminado. Mas agora vamos aos ajustes para usarmos JPA neste projeto. Siga para a pasta onde descompactamos o EclipseLink e copie os jars eclipselink.jar e javax.persistence_2.0.3.v201010191057.jar para a lib do tomcat:

Agora voltamos ao projeto, mas desta vez na pasta src: crie uma pasta chamada META-INF lá:

Crie então dentro desta pasta o arquivo persistence.xml:



	
		
		java:comp/env/jdbc/agenda-ds
		
		exemplo.model.Contato
		exemplo.model.Endereco
		
		
			
		
	

Nas classes Contato e Endereco faça as seguintes modificações:

package exemplo.model;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
public class Contato {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private long id;
	private String nome;
	private String telefone;
	@OneToMany
	private List enderecos;

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getNome() {
		return nome;
	}

	public void setNome(String nome) {
		this.nome = nome;
	}

	public String getTelefone() {
		return telefone;
	}

	public void setTelefone(String telefone) {
		this.telefone = telefone;
	}

	public List getEnderecos() {
		return enderecos;
	}

	public void setEnderecos(List enderecos) {
		this.enderecos = enderecos;
	}
}

package exemplo.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
public class Endereco {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private long id;
	private String rua;
	private int numero;
	private String complemento;
	@ManyToOne
	@JoinColumn(name = "CONTATO_ID", referencedColumnName = "ID")
	private Contato contato;

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getRua() {
		return rua;
	}

	public void setRua(String rua) {
		this.rua = rua;
	}

	public int getNumero() {
		return numero;
	}

	public void setNumero(int numero) {
		this.numero = numero;
	}

	public String getComplemento() {
		return complemento;
	}

	public void setComplemento(String complemento) {
		this.complemento = complemento;
	}

	public Contato getContato() {
		return contato;
	}

	public void setContato(Contato contato) {
		this.contato = contato;
	}
}

Como estamos no tomcat e não num AppServer completo, vamos criar um enum utilitário para recuperarmos uma fererência ao EntityManager:

package exemplo.util;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public enum JPAUtil {

	INSTANCE;

	private EntityManagerFactory emf;

	JPAUtil() {
		emf = Persistence.createEntityManagerFactory("agenda-pu");
	}

	public EntityManager getEntityManager() {
		return emf.createEntityManager();
	}
}

Usaremos ele no servlet Salvar:

package exemplo.controller;

import java.io.IOException;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import exemplo.model.Contato;
import exemplo.util.JPAUtil;

@WebServlet("/vaisalvar")
public class Salvar extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		EntityManager em = JPAUtil.INSTANCE.getEntityManager();

		String sId = request.getParameter("id").trim();
		String sNome = request.getParameter("nome");
		String sTelefone = request.getParameter("telefone");

		Contato c = null;
		if (!"".equals(sId))
			c = em.find(Contato.class, Long.parseLong(sId));
		else
			c = new Contato();

		c.setNome(sNome);
		c.setTelefone(sTelefone);
		EntityTransaction t = em.getTransaction();
		t.begin();
		em.persist(c);
		t.commit();

		// devemos ainda redirecionar para a tela de resultado
		request.getRequestDispatcher("/sucesso.jsp").forward(request, response);
	}

}

Siga para o sucesso.jsp e adicione o seguinte código a seguir. Ele serve apenas para desviar o usuário da tela atual. Aproveite e crie logo o index.jsp que vai a seguir:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>

Sucesso!
</head>
<body>
	

Sucesso!

<script type="text/javascript"> setTimeout(function() { window.location.href = 'index.jsp'; }, 3000); </script> </body> </html>

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>

Agenda
</head>
<body>
	Novo Contato
Listar Contatos </body> </html>

Criaremos agora o servlet de listagem de contatos. Faça do mesmo jeito que você fez com o servlet anterior, e mude o conteúdo dele para o que vem abaixo:

package exemplo.controller;

import java.io.IOException;
import java.util.List;

import javax.persistence.EntityManager;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import exemplo.model.Contato;
import exemplo.util.JPAUtil;

/**
 * Servlet implementation class Listar
 */
@WebServlet("/vailistar")
public class Listar extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		EntityManager em = JPAUtil.INSTANCE.getEntityManager();

		List contatos = //
		em.createQuery("select c from Contato c", Contato.class)//
				.getResultList();

		request.setAttribute("contatos", contatos);
		request.getRequestDispatcher("/listar_contatos.jsp")//
				.forward(request, response);
	}
}

O workspace deve estar ficando assim:

Observe que desta vez o redirecionamento é para listar_contatos.jsp e não para o sucesso.jsp; Em se tratando de Aplicações Action-Based, no caso de listagens devemos navegar para o controlador primeiro e só então irmos para a apresentação.

Este jsp ainda não existe, então vá e crie-o com o seguinte conteúdo:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%--declaração da taglib --%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>

Listar Contatos
</head>
<body>
	
</body>
</html>

Aqui um ponto interessante: este jsp deve surgir para você com um erro na liha de declaração da taglib. Deve estar mais ou menos assim:

A correção deste problema é muito simples, siga para a pasta do tomcat, entre em webapps>examples>WEB-INF/lib:

Copie e cole os dois jars ali presentes para a pasta WebContent>WEB-INF>lib do projeto:

Feito isso, um simples project>clean deve sumir com o alerta de erro:

Dando continuidade ao jsp, modifique-o para ficar assim:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%--declaração da taglib --%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>

Listar Contatos
</head>
<body>
	
				
					
Id Nome Telefone Endereços
${contato.id} ${contato.nome} ${contato.telefone} Ver | Novo
Voltar </body> </html>

Com jstl fica muito melhor manipular os dados que o servidor encaminha para o jsp.

Observe que criaremos dois novos servlets, um para listar endereços e outro para salvar endereços, além dos jsp's com o formulário para receber os dados de endereço e o que recebe a listagem. Primeiro os arquivos do fluxo de listagem:

package exemplo.controller;

import java.io.IOException;
import java.util.List;

import javax.persistence.EntityManager;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import exemplo.model.Endereco;
import exemplo.util.JPAUtil;

/**
 * Servlet implementation class ListarEndereco
 */
@WebServlet("/vailistarendereco")
public class ListarEndereco extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		String sId = request.getParameter("contatoid");
		long idContato = Long.parseLong(sId);
		List enderecos = null;

		EntityManager em = JPAUtil.INSTANCE.getEntityManager();
		String q = "select e from Endereco e where e.contato.id = :id";
		enderecos = em.createQuery(q, Endereco.class)//
				.setParameter("id", idContato).getResultList();

		request.setAttribute("enderecos", enderecos);
		request.getRequestDispatcher("/listar_enderecos.jsp")//
				.forward(request, response);

	}

}

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>

Listar Endreços
</head>
<body>
	

Listar Endreços do Contato ${param.contatoid}

Id Rua Número Complemento
${e.id} ${e.rua} ${e.numero} ${e.complemento}
Voltar </body> </html>

Agora o fluxo de salvamento:

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>

Salvar Endereços

</head>
<body>
	

</body> </html>

package exemplo.controller;

import java.io.IOException;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import exemplo.model.Contato;
import exemplo.model.Endereco;
import exemplo.util.JPAUtil;

/**
 * Servlet implementation class SalvarEndereco
 */
@WebServlet("/vaisalvarendereco")
public class SalvarEndereco extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		String sIdContato = request.getParameter("idcontato");
		String sId = request.getParameter("id");
		String rua = request.getParameter("rua");
		String sNumero = request.getParameter("numero");
		String complemento = request.getParameter("complemento");

		EntityManager em = JPAUtil.INSTANCE.getEntityManager();

		Endereco e = null;
		if (!"".equals(sId))
			e = em.find(Endereco.class, Long.parseLong(sId));
		else {
			e = new Endereco();
			long contatoId = Long.parseLong(sIdContato);
			Contato c = em.find(Contato.class, contatoId);
			e.setContato(c);
		}

		e.setRua(rua);
		e.setNumero(Integer.parseInt(sNumero));
		e.setComplemento(complemento);

		EntityTransaction tx = em.getTransaction();
		tx.begin();
		em.persist(e);
		tx.commit();

		request.getRequestDispatcher("/sucesso.jsp").forward(request, response);
	}
}

Com isso você terá uma aplicação completamente funcional, simples de entender e que mostra o básico de um projeto web com JPA. Reinicie o tomcat e visite a aplicação em http://localhost:8080/agenda e tente salvar alguns contatos e adicionar endereços.

Detalhes relevantes mas que não mostrei aqui:

  1. Usar JPA integrado com JEE fullstack é muito mais simples do que mostrei aqui; não precisamos, por exemplo, baixar as libs do EclipseLink, pois normalmente o AppServer já tem uma biblioteca de propósito equivalente dentro de sua runtime básica.
  2. O trecho transacional, visto em todos os casos em que precisamos salvar entidades, é desnecessário caso usemos o EntityManager de dentro de um EJB.
  3. A propriedade especial que colocamos no persistence.xml não é padrão, caso a sua implementação de JPA seja o Hibernate, existe uma chave equivalente mas o nome é outro.

Espero que o tutorial lhe seja útil, hoje paramos por aqui.

Até mais.

Nenhum comentário :

Postar um comentário