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

Comentários

Postagens mais visitadas deste blog

E Esse Tal de Nano Service?

Executar Audio em Java Swing

Validando Email em Java Com e Sem expressão Regular