quinta-feira, 4 de agosto de 2011

Exemplo de JSF+JPA

Fala galera!
Eu Bruno Rafael venho aqui neste para lhes mostrar um exemplo básico para quem tem interesse em começar a desenvolver aplicações web com JSF e JPA.

Então vamos lá!

Criei uma aplicação simples, sem nenhum framework adicional alem de JSF (1.2_04) e JPA (toplink), estou rodando com Java 6 e Tomcat 6.
Os unicos jars no classpath da aplicação são:
  • jsf-impl.jar
  • jsf-api.jar
  • jstl.jar
  • toplink-essentials-agent.jar
  • toplink-essentials.jar
Os únicos arquivos alterados na aplicação foram o web.xml, que ficou assim:
<?xml version=”1.0″ encoding=”UTF-8″?>
<web-app id=”WebApp_ID” version=”2.4″ xmlns=”http://java.sun.com/xml/ns/j2ee” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd”>
<display-name>jsfjpa</display-name>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>
Ou seja, apenas adicionei o servlet do JSF.
o faces-config.xml que ficou assim:
<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE faces-config PUBLIC
“-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN”
“http://java.sun.com/dtd/web-facesconfig_1_1.dtd”>
<faces-config>
<navigation-rule>
<navigation-case>
<from-outcome>home</from-outcome>
<to-view-id>/home.jsp</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
<managed-bean>
<managed-bean-name>exemplo</managed-bean-name>
<managed-bean-class>br.com.urubatan.jsfjpa.mbean.ExemploMBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>id</property-name>
<property-class>java.lang.Long</property-class>
<value>#{param.id}</value>
</managed-property>
</managed-bean>
</faces-config>
Ou seja, tem um ManagedBean e um navigation rule apenas.
A entidade a ser persistida (fiz apenas com uma para o exemplo):
package br.com.urubatan.jsfjpa.data;

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

@Entity
public class Cliente {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long    id;
private String    name;

public Long getId() {
return id;
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
uma entidade simples, com o minimo de anotações possivel.
e o Managed Bean do exemplo:
package br.com.urubatan.jsfjpa.mbean;

import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceContext;

import br.com.urubatan.jsfjpa.data.Cliente;

public class ExemploMBean {
@PersistenceContext(name = "jsfjpa2")
private EntityManager    em;
private List<Cliente>    clientes;
private Cliente            cliente;
private Long            id;

public Long getId() {
return id;
}

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

public List<Cliente> getClientes() {
return clientes;
}

public void setClientes(List<Cliente> clientes) {
this.clientes = clientes;
}

public Cliente getCliente() {
return cliente;
}

public void setCliente(Cliente cliente) {
this.cliente = cliente;
}

@PostConstruct
public void init() {
if (id != null)
cliente = em.find(Cliente.class, id);
else cliente = new Cliente();
list();
}

@SuppressWarnings("unchecked")
private void list() {
clientes = em.createQuery("select c from Cliente c").getResultList();
}

@PreDestroy
public void destroy() {
}

public String save() {
EntityTransaction t = em.getTransaction();
t.begin();
if (cliente.getId() == null) {
em.persist(cliente);
} else {
em.merge(cliente);
}
t.commit();
return "home";
}

public String delete() {
EntityTransaction t = em.getTransaction();
t.begin();
if (cliente.getId() != null) {
Cliente c = em.getReference(Cliente.class, cliente.getId());
em.remove(c);
}
t.commit();
return "home";
}
}
Para que isto funcione é preciso registrar no tomcat um “Resource” que sera uma factory de EntityManager, eu fiz isto utilizando um arquivo “context.xml” no diretório META-INF da aplicação, pois achei mais fácil de portar para o exemplo, o context.xml ficou assim:
<Context>
<Resource auth=”Container” factory=”br.com.urubatan.jsfjpa.util.PersistenceManagerObjectFactory” name=”jsfjpa2″ type=”javax.persistence.EntityManagerFactory”
unitName=”jsfjpa” />
</Context>
E a classe PersistenceManagerObjectFactory, fica mais ou menos assim:
package br.com.urubatan.jsfjpa.util;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.spi.ObjectFactory;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.apache.naming.ResourceRef;

public class PersistenceManagerObjectFactory implements ObjectFactory {
private String                                        unitName;
private static Map    factoryMap    = new HashMap();

public String getUnitName() {
return unitName;
}

public void setUnitName(String unitName) {
this.unitName = unitName;
}

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
RefAddr un = ((ResourceRef) obj).get("unitName");
EntityManagerFactory emf = getFactory((String) un.getContent());
return emf.createEntityManager();
}

private EntityManagerFactory getFactory(String unitName) {
EntityManagerFactory factory = (EntityManagerFactory) factoryMap.get(unitName);
if (factory == null) {
factory = Persistence.createEntityManagerFactory(unitName);
factoryMap.put(unitName, factory);
}
return factory;
}
}
Eu utilizei genérics no código que esta no zip do exemplo, mas precisei remover aqui do código no blog por que o editor do wordpress não se da muito bem com < no código.
E tudo deve estar funcionando agora.
Quer dizer, quase …
O Tomcat 6 tem um zip de nome annotations-api.jar, que contem uma versão baleada das anotações do JPA, então editei este zip, removi o diretório persistence de dentro dele, e movi os jars do toplink para o diretório lib do tomcat, isto fez o exemplo funcionar.
Para os que acharem que isto é trabalho demais, podemos mudar um pouco a abordagem também, criamos um InjectionProvider para o JSF, com o código a seguir (Eu copiei o código do SVN do Java Server Faces RI e adicionei o suporte direto a JPA):
package br.com.urubatan.jsfjpa.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceContext;
import javax.servlet.ServletContext;

import org.apache.AnnotationProcessor;

import com.sun.faces.spi.InjectionProvider;
import com.sun.faces.spi.InjectionProviderException;

public class Tomcat6JPAInjectionProvider implements InjectionProvider {
private final ServletContext                        servletContext;
private static Map<String, EntityManagerFactory>    factoryMap    = new HashMap<String, EntityManagerFactory>();

public Tomcat6JPAInjectionProvider(ServletContext servletContext) {
this.servletContext = servletContext;
}
public void inject(Object managedBean) throws InjectionProviderException {
try {
getProcessor().processAnnotations(managedBean);
} catch (Exception e) {
if (!(e instanceof NamingException)) throw new InjectionProviderException(e);
}
for (Field f : managedBean.getClass().getDeclaredFields()) {
if (f.isAnnotationPresent(PersistenceContext.class)) {
EntityManager em = getEntityManager(f.getAnnotation(PersistenceContext.class));
}
}
for (Method m : managedBean.getClass().getDeclaredMethods()) {
if (m.isAnnotationPresent(PersistenceContext.class)) {
}
}
}

private EntityManager getEntityManager(PersistenceContext annotation) {
return null;
}

private EntityManagerFactory getFactory(String unitName) {
EntityManagerFactory factory = factoryMap.get(unitName);
if (factory == null) {
factory = Persistence.createEntityManagerFactory(unitName);
factoryMap.put(unitName, factory);
}
return factory;
}

public void invokePreDestroy(Object managedBean) throws InjectionProviderException {
try {
getProcessor().preDestroy(managedBean);
} catch (Exception e) {
throw new InjectionProviderException(e);
}
}

public void invokePostConstruct(Object managedBean) throws InjectionProviderException {
try {
getProcessor().postConstruct(managedBean);
} catch (Exception e) {
throw new InjectionProviderException(e);
}
}

private AnnotationProcessor getProcessor() {
return ((AnnotationProcessor) servletContext.getAttribute(AnnotationProcessor.class.getName()));
}
}
E adicionamos um parametro no web.xml que informa ao JSF que deve utilizar este provider em vez do default assim:
<context-param>
<param-name>com.sun.faces.injectionProvider</param-name>
<param-value>br.com.urubatan.jsfjpa.util.Tomcat6JPAInjectionProvider</param-value>
</context-param>
depois disto é necessário alterar o managedbean, a anotação @PersistenceContext, deve utilizar o parametro unitName para especificar qual a persistence unit que utilizar, e não o name, ja que o primeiro é o nome da persistence unit, o segundo é o nome JNDI a ser utilizado.

Utilizando esta segunda abordagem, ainda é possivel alterar alguns parametros de comportamento, como por exemplo, pegar o nome do campo, quando o unitName não for informado, mas isto vou deixar para vocês brincarem um pouquinho …

Com isto temos com bem pouco código, um cadastro simples utilizando JSF e JPA, com injeção de dependencias, uma grande flexibilidade na forma de trabalhar, e uma dica importante, de como acessar páginas utilizando GET com JSF em vez de POST, como parece ser a unica forma a primeira vista.

O que acharam desta forma de programar, utilizando DI e apenas seguindo as especificações e padrões Java EE, e ainda assim sem a necessidade de um container Java EE completo :D

Nenhum comentário:

Postar um comentário

Admin: Bruno

Olá Galera! muito grato por estarem acessando nosso blog. Espero que seja possível transmitir de forma compreensível um pouco de meus conhecimentos em programação, para esta comunidade de desenvolvedores que cresce cada vez mais! Espero que Gostem! Abraço! E meu enorme obrigado à Renato Simões, Átila Soares,Wanderson Quinto, Emerson e a toda galera que sempre ajudou meu sincero obrigado....
Especialmente a Natalia Failache e Rita de Cassia que sempre apoiaram este sonho....

De seu amigo Bruno Rafael.