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<Endereco> 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<Endereco> getEnderecos() { return enderecos; } public void setEnderecos(List<Endereco> 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> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Salvar Contato</title> <style type="text/css"> form.box p label { display: block; } form.box p input { width: 200px; } </style> </head> <body> <form class="box" action="vaisalvar"> <p> <label>id</label> <input name="id" /> </p> <p> <label>nome</label> <input name="nome" /> </p> <p> <label>telefone</label> <input name="telefone" /> </p> <p> <input type="submit" value="Salvar"/> </p> </form> </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:
<Resource name="jdbc/agenda-ds" type="javax.sql.DataSource" auth="Container" maxActive="10" maxIdle="3" maxWait="10000" username="sa" password="sa" driverClassName="org.h2.Driver" url="jdbc:h2:~/agenda" />
Agora siga para a pasta do projeto, vamos para WebContent>META-INF. Lá crie um arquivo chamado context.xml com o seguinte conteúdo:
<?xml version="1.0" encoding="UTF-8"?> <Context> <ResourceLink name="jdbc/agenda-ds" global="jdbc/agenda-ds" /> </Context>
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:
<?xml version="1.0" encoding="UTF-8" ?> <persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="agenda-pu" transaction-type="JTA"> <!-- nome do DataSource mapeado pelo ResourceLink --> <jta-data-source>java:comp/env/jdbc/agenda-ds</jta-data-source> <!-- classes de modelo --> <class>exemplo.model.Contato</class> <class>exemplo.model.Endereco</class> <!-- propriedade especial do EclipseLink --> <properties> <property name="eclipselink.ddl-generation" value="create-tables" /> </properties> </persistence-unit> </persistence>
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<Endereco> 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<Endereco> getEnderecos() { return enderecos; } public void setEnderecos(List<Endereco> 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> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Sucesso!</title> </head> <body> <h1>Sucesso!</h1> <script type="text/javascript"> setTimeout(function() { window.location.href = 'index.jsp'; }, 3000); </script> </body> </html>
<!-- index.jsp --> <%@ 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> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Agenda</title> </head> <body> <a href='salvar_contato.jsp'>Novo Contato</a><br/> <a href='vailistar'>Listar Contatos</a> </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<Contato> 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> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Listar Contatos</title> </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> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Listar Contatos</title> </head> <body> <table> <thead> <tr> <th>Id</th> <th>Nome</th> <th>Telefone</th> <th>Endereços</th> </tr> </thead> <tbody> <c:if test="${not empty contatos}"> <c:forEach items="${contatos}" var="contato"> <tr> <td>${contato.id}</td> <td>${contato.nome}</td> <td>${contato.telefone}</td> <td> <!-- --> <a href="vailistarendereco?contatoid=${contato.id}">Ver</a> |<!-- --> <a href="salvar_endereco.jsp?contatoid=${contato.id}">Novo</a> </td> </tr> </c:forEach> </c:if> </tbody> </table> <a href="index.jsp">Voltar</a> </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<Endereco> 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> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Listar Endreços</title> </head> <body> <h1>Listar Endreços do Contato ${param.contatoid}</h1> <table> <thead> <tr> <th>Id</th> <th>Rua</th> <th>Número</th> <th>Complemento</th> </tr> </thead> <tbody> <c:if test="${not empty enderecos}"> <c:forEach items="${enderecos}" var="e"> <tr> <td>${e.id}</td> <td>${e.rua}</td> <td>${e.numero}</td> <td>${e.complemento}</td> </tr> </c:forEach> </c:if> </tbody> </table> <a href="vailistar">Voltar</a> </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> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Salvar Endereços</title> <style type="text/css"> form.box p label { display: block; } form.box p input { width: 200px; } </style> </head> <body> <form class="box" action="vaisalvarendereco"> <p> <label>Id Contato</label> <input name="idcontato" value="${param.contatoid}" readonly /> </p> <p> <label>Id</label> <input name="id" /> </p> <p> <label>Rua</label> <input name="rua" /> </p> <p> <label>Número</label> <input name="numero" /> </p> <p> <label>Complemento</label> <input name="complemento" /> </p> <p> <input type="submit" value="Salvar" /> </p> </form> </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:
- 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.
- O trecho transacional, visto em todos os casos em que precisamos salvar entidades, é desnecessário caso usemos o EntityManager de dentro de um EJB.
- 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.
Cade o codigo no github?
Isso é material clássico, não é do tempo do github não, rsrsrs.
http://code.google.com/p/sombriks-hq/source/browse/trunk/samples/agendajpa/
Certo seria um cvs no sourceforge!