Blog do Urubatan
msgbartop
Desenvolvedor, Palestrante, Escritor, Nerd Assumido e Pai do Marcus :D
msgbarbottom

03 May 07 Produtividade com JSF + JPA sem nenhum outro framework, com DI e edição via GET em vez de POST

Bom, eu vi hoje em um post em outro blog, que o JSF-RI 1.2 possui suporte para anotações e DI, então resolvi fazer um pequeno teste de uma APP apenas com a JSF-RI e a RI do JPA para ver no que da, e até que ficou bem legal …

Deem uma olhada nisto, o JSF RI tem suporte a injeção de dependencias plugavel, ja tem suporte a GlassFish, Tomcat6 e Jetty6 de fabrica, e como veremos é bem fácil de extender isto …

A DI segue o mesmo estilo do Servlet 2.5, e EJB 3, ou seja, é feira via as anotações:

@PostConstrut, @PreDestroy, @PersistenceContext, @Resource, @EJB, …

Por padrão no tomcat, é sempre feito um lookup JNDI para buscar os objetos, mas vamos deixar os detalhes sórdidos para mais tarde …

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

Se quiserem baixar o código que escrevi no exemplo (Não tem nada alem do que esta aqui no Post, é só isto mesmo), é só clicar aqui.

If you enjoyed this post, make sure you subscribe to my RSS feed!

Tags: , , ,

Reader's Comments

  1. |

    Ficou bastante simples e leve, e o legal é que não precisa daquele monte de “.JARs”, que fica bastante interessante quando você precisa desenvolver e implantar algo rápido num computador remoto onde você possui limitações de uso de banda.

    Reply to this comment
  2. |

    Esse ficará em meus favoritos …
    Não conheço JPA …
    Está bem legal, simples e fácil de aprender …

    Reply to this comment
  3. |

    Bem bacaninha o exemplo. Interessante a abordagem, porém usar get caimos no velho problema de limitação de tamanho de caracteres. Sei que a princípio foi que vc viu, mas se souber se via post tem como usar, por favor poste ao menos um comentário aqui, ou um outro post mesmo qeu seja com o mesmo código, mas com ele via post =)

    Valeu rodrigo!

    Reply to this comment
  4. |

    a unica coisa via GET no exemplo é o link para edição, os formulários continuam sendo via POST, e isto não tem mesmo como mudar no JSF :D
    acho que me expressei mal no texto …
    mas a idéia é que muita gente pensa que com JSF náo é possivel acessar uma página passando parametros via GET, só quis demonstrar que isto é possivel, mas os formulários não tem mesmo como mudar, é obrigatóriamente post

    Reply to this comment
  5. |

    Urubatan, no é só do jsf 1.2?

    Reply to this comment
  6. |

    sim, a injeção de dependencias é só no 1.2, a parte de fazer via get funciona no 1.1 também :D

    Reply to this comment
  7. |

    Ficou ruim minha pergunta, eu esqueci de escapar os < e >.

    Reiterando (eu sei que vc jah respondeu):

    “Urubatan, <redirect /> <navigation-case /> no é só do jsf 1.2?”

    Reply to this comment
  8. |

    Cara muito bom Post.

    Reply to this comment
  9. |

    Muito bom. Estou estudando bastante. Resolví tentar rodar, adaptando algumas coisas para minha instalação. Encontrei um único problema, no home.jsp, em binding=”#{exemplo.inputName}”. Alguma sugestão?

    Reply to this comment
  10. |

    Opa, é só remover o atributo, eu tinha feito um teste com isto e esqueci de deletar antes de subir o exemplo :D

    Reply to this comment
  11. |

    [...] o MyFaces 1.2 já nasceu com alguns problemas, como por exemplo, o que comentei no post sobre JSF + DI, funciona meia boca no MyFaces … Ja que o MyFaces só possui um adaptador de injeção de [...]

    Reply to this comment
  12. |

    [...] Produtividade com JSF + JPA sem nenhum outro framework, com DI e edição via GET em vez de POST 341 – acessos [...]

    Reply to this comment
  13. |

    [...] de começar, quem quiser usar JSF com JPA é só dar uma olhada neste post que escrevi a algum [...]

    Reply to this comment
  14. |

    Não estou conseguindo compilar no Eclipse, pois não consigo encontrar as classes com.sun.faces.spi.InjectionProvider e InjectionProviderException. Estou usando a implemtação da Sun, jsf-impl.jar/jsf-api.jar e as duas do Toplink.

    Alguma dica do que possa estar esquecendo ?!

    Grato!

    Reply to this comment
  15. |

    elas estão no arquivo jsf-api.jar, mas tem que ser JSF 1.2, no 1.1 estes arquivos não existem

    Reply to this comment
  16. |

    Este bug do tomcat ja foi identificado (http://issues.apache.org/bugzilla/show_bug.cgi?id=43425), não sei quando mas em breve ja esta disponivel. Com esse ObjtectFactory que vc teve que fazer a mão (subrir) o bug..disponibiliza o EntityManager via JNDI direto ?

    Reply to this comment
  17. |

    sim, com este ObjectFactory ele disponibiliza o EntityManager via JNDI direto, mas um application managed EntityManager

    Reply to this comment
  18. |

    Como assim “um” ? Cada vez que vc obter via jndi tem que retornar um objeto diferente ! Outra coisa Rodrigo…o nome de busca do jndi vc coloca no DD com a nova config de resource-ref:

    jpa/em
    cirrus

    Mas vc não fez assim ? qual seria o nome de busca ?

    Reply to this comment
  19. |

    Desculpa ai oo blog retirou o codigo…vou colocar como chaves:

    [persistence-context-ref]
    [persistence-context-ref-name[jpa/em

    Reply to this comment
  20. |

    é só olhar no código, cada vez que um objeto é solicitado ele cria um entity manager

    Reply to this comment
  21. |

    acho melhor no blog.. ehhehehe a minha duvida pode ser as dos outros iaheuiaheiuahe
    como assim extesoes do toplink?

    Reply to this comment
  22. |

    as mesmas que tu passou o link

    Reply to this comment
  23. |

    Boa tarde Uburatan, sempre acompanho seus artigos e já aprendi muito com eles. Sou um iniciante na área de desenvolvimento em java, estou com um problema no import, que não está reconhecendo aqui:

    javax.annotation.PostConstruct;
    javax.annotation.PreDestroy;

    Pode me ajudar?
    Abraços.

    Reply to this comment
  24. |

    Olá Urubatan…muito bom seu Blog, eu venho tendo problemas com lazy exception por causa de coleções de objetos dentro de outro, pelo que vi esse Injection deve resolver meu problema, mas eu não consegui fazer seu exemplo funcionar! Dá erro no import: “import org.apache.naming.ResourceRef;” ele naum acha esta classe e olha que eu coloquei todos os JARs descritos acima no CLASSPATH, tem alguma dica?
    []s

    Reply to this comment
  25. |

    [...] [...]

    Reply to this comment
  26. |

    [...] [...]

    Reply to this comment
  27. |

    Show de bola esse topico, mas eu to tentando rodar aqui e nao vai nem com reza….
    to usando o Jboss 4.2.3
    vasculhando na NET vi q este jboss nao aceita DI
    da sempre o mesmo erro:
    [JBossInjectionProvider] Injection failed on managed bean

    to baixando o jboss 5 enquanto escrevo aqui…

    sabe se vai funcionar?? ou é melhor eu desistir do Jboss ??
    sou iniciante em JSF

    Reply to this comment
  28. |

    cara eu estou tentando faz tempo fazer um sisteminha só pra conhecimento utilizando servlets com jpa!

    Meu problema é o seguinte:
    Quando minha aplicação rodar eu quero que a classe responsavel pela criação do entitymanager seja acionada pq eu crio minhas entidades através do persistence.xml.

    No desktop isso é bem tranquilo pq quando executo o método main mando criar só que no sistema web não tenho idéia de como isso funciona!!

    espero que vc possa me ajudar brigado e ótimo blog.

    Reply to this comment
  29. |

    Urubatan, daria pra usar o tomcat como container EJB? já que ele estará com as bibliotecas JPA Toplink????
    se eu colocar a anotação @EJB vai funcionar td direitinho? ou eu estou viajando na maionese mesm? brigadu :D

    Reply to this comment
    • |

      Olha, com ejb3 é possível subir um micro container EJB dentro do Tomcat, mas isto não é muito trivial …
      por padrão, o tomcat não pode ser utilizado como um container EJB não …

      Reply to this comment

Leave a Comment