Programador ajudante e aprendiz da comunidade open source.

JPA e VRaptor 3

Atualizado em 4 de Janeiro de 2011.

Sabemos que hoje em dia é fundamental termos uma framework de persistência nos poupando o trabalho de criação de tabelas, trocas de chave e afins. Se você quer produtividade e um conjunto incrível de funcionalidades, posso dizer que o Hibernate é uma ótima escolha.

Objetivo:

Criar um projeto no qual iremos configurar e utilizar a API JPA com controle de transação automatizada junto ao VRaptor 3.

Começaremos criando um arquivo de configuração para o nosso banco de dados.

Criando o arquivo de configuração (persistence.xml):

<?xml version="1.0" encoding="UTF-8"?>

<persistence>
   <persistence-unit name="default">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>

      <properties>
        <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
        <property name="hibernate.connection.username" value="root"/>
        <property name="hibernate.connection.password" value="root"/>
        <property name="hibernate.connection.url" value="jdbc:mysql://127.0.0.1:3306/banco"/>
        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect"/>

        <property name="hibernate.hbm2ddl.auto" value="update"/>

      </properties>
   </persistence-unit>
</persistence>

Linha 4: temos uma unidade de persistência chamada default;
Linha 5: usamos o provider do Hibernate;
Linha 8: utilizamos o banco de dados MySQL e seu driver;
Linha 9-10: o banco esta com nome de usuário e senha "root";
Linha 11: conexão localhost, porta padrão 3306 no schema "banco";
Linha 2: o dialeto usado é o InnoDB do MySQL;
Linha 14: usamos o HBM para gerar as tabelas de automáticamente de acordo com as entidades anotadas com @Entity. O valor update adiciona colunas no banco à medida que você adiciona ou altera a entidade, porém não as retira caso deixem de existir.

É importante ressaltar que deverá ser usado o InnoDB, pois ele é transacional, ou seja, é possível trabalhar com trasações ao contrário do MyISAM.

Nossa configuração esta pronta, porém precisamos de uma classe que utilize estas configurações e possibilite nossa aplicação conectar ao banco.

À princípio criaríamos um EntityManagerUtil desta forma:

public class EntityManagerUtil {

    private static EntityManagerFactory emf;

    public static EntityManager getEntityManager() {
        if (emf == null) {
            emf = Persistence.createEntityManagerFactory("default");
        }

        return emf.createEntityManager();
    }

    public static void close() {
        emf.close();
    }

}

E em nossos DAOs pegaríamos a conexão da seguinte forma:

@Component
public class UsuarioDao {

    private EntityManager manager = EntityManagerUtil.getEntityManager();
    ...

}

Através da nossa classe utilitária seria possível obter o EntityManager e assim a Transaction, mas teríamos algo indesejado, o acoplamento.

Nosso DAO esta se preocupando com a forma de obtê-lo e este código esta amarrado à própria classe. O ideal seria receber este objeto, através de um método set, construtor ou injeção de dependência e é ai que o VRaptor entra para nos ajudar. (:

No lugar dessa classe EntityManagerUtil vamos registrar um componente opcional do VRaptor.

Criando o componente CustomProvider:

public class CustomProvider extends SpringProvider {

    @Override
    protected void registerCustomComponents(ComponentRegistry registry) {
        registry.register(EntityManagerCreator.class, EntityManagerCreator.class);
        registry.register(EntityManagerFactoryCreator.class, EntityManagerFactoryCreator.class);
        registry.register(JPATransactionInterceptor.class, JPATransactionInterceptor.class);
    }

}

Nossa classe estende SpringProvider e sobrescreve o método registerCustomComponents para registrar o EntityManagerCreator, FactoryCreator e JPATransaction nos disponibilizando assim o EntityManager de forma transparente.

Depois de criar esta classe, devemos registrá-la como um provider no web.xml:

<context-param>
   <param-name>br.com.caelum.vraptor.provider</param-name>
   <param-value>br.com.wbotelhos.provider.CustomProvider</param-value>
</context-param>

Usando o nosso provider podemos decidir se iremos registrar o componente de controle de transação ou não. Mas o VRaptor ainda faz mais por nós e como já era esperado que iríamos querer todo estes componentes, o VRaptor já incluiu em seu pacote util um outro pacote chamado jpa que já registra estes três componentes e que por sua vez substituirá a nossa CustomProvider bastando trocar o registro do web.xml para apontar para o mesmo:

<context-param>
    <param-name>br.com.caelum.vraptor.packages</param-name>
    <param-value>br.com.caelum.vraptor.util.jpa</param-value>
</context-param>

Agora você pode retirar qualquer begin e commit do seu código, pois o VRaptor irá se preocupar com isso por você. Quanto ao rollback você precisa de mantê-lo, pois se der um erro em alguma operação, todas as anteriores serão desfeita, já que o VRaptor irá manter este conjunto de ações em uma única transação de forma transparente.

Pronto! Agora o VRaptor já sabe que deve buscar e utilizar o nosso persistence.xml, que deve estar localizado no classpath dentro de src/META-INF.

O VRaptor por padrão procura por uma persistence-unit chamada "default". Certifique-se de ter criado com este nome: <persistence-unit name="default" ...

Com isso o DAO recebe por injeção o EntityManager evitando o alto acoplamento:

@Component
public class UsuarioDao {

    private EntityManager manager;

    UsuarioDao (EntityManager manager) {
        this.manager = manager;
    }

    ...

}

Procure se possível utilizar um DAO genérico, evitando repetições de código.

Criando o schema e populando o banco:

Existe um arquivo, banco.sql, dentro do projeto já com o código SQL para criar e popular o banco, mas se preferir poderá rodar a aplicação e o Hibernate tratará de criar todo o banco devido ao SchemaExport (hbm2ddl), ficando-lhe apenas a tarefa de populá-lo.

Vamos criar uma entidade Usuario:

@Entity
public class Usuario {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String nome;
    private String email;

    // getters and setters

}

Dissemos que Usuario é uma entidade e que id é a chave, sendo esta incrementada pelo banco.

Com a entidade criada, podemos criar um método para colocarmos o Hibernate em ação:

public Usuario loadByNome(String nome) throws Exception {
    try {
        Query query = manager.createQuery("from Usuario where nome = :nome");
        query.setParameter("nome", nome);
        Usuario usuario = (Usuario) query.getSingleResult();
        return usuario;
    } catch (NoResultException e) {
        throw new Exception("Nenhum resultado!");
    }
}

Na consulta acima buscamos pelo nome passado por parâmetro e retornamos um Usuario.

Foi colocado um rollback para ilustrar o uso, apesar de não ser necessário neste caso.

Vamos criar o controller para fazer a chamada desse método e disponibilizar o resultado em tela:

@Resource
public class UsuarioController {

    private Result result;
    private UsuarioDao usuarioDao;

    public UsuarioController(Result result, UsuarioDao usuarioDao) {
        this.result = result;
        this.usuarioDao = usuarioDao;
    }

    @Get
    @Path("/usuario/consultar")
    public Usuario consulta(Usuario entity) {
        try {
            return usuarioDao.loadByNome(entity.getNome());
        } catch (Exception e) {
            result.include("message", e.getMessage());
        }
        return null;
    }

}

Fizemos a injeção dos recursos que necessitamos e chamamos o método que nos retorna um usuário de acordo com o nome passado.

Por fim iremos criar as páginas de consulta e resultado.

Página de consulta (index/index.jsp):

<form action="<c:url value='/usuario/consultar'/>" method="get">
    Consultar: <input type="text" name="entity.nome"/>
    <input type="submit"/>
</form>

Página de resultado (usuario/resultado.jsp):

${message}

<c:if test="${usuario != null}">
    ID: ${usuario.id}
    Nome: ${usuario.nome}
    E-mail: ${usuario.email}
</c:if>

Os componentes utilitários do VRaptor ainda possui outros recursos como Hibernate Session e SessionFactory, e creio que cada vez mais irá facilitar nossas vidas.

Quero agradecer a ajuda do Mário Peixoto, que sanou várias dúvidas.

Link do projeto:

http://github.com/wbotelhos/jpa-vraptor

  1. Ailton 18 Set 2014 06:58

    Olá!
    Parabéns pelo post, mesmo tendo alguns aninhos me ajudou muuuuuito!
    Bom, minha dúvida é o seguinte: quando faço a busca por uma valor que não existe, aparece corretamente no navegador a mensagem nenhum resultado, porém no meu console aparece o erro: Set 18, 2014 6:46:55 AM org.apache.catalina.core.StandardWrapperValve invoke
    SEVERE: Servlet.service() for servlet [default] in context with path [/jpa] threw exception
    java.lang.IllegalStateException: Transaction not active
    at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:47)
    at br.com.caelum.vraptor.util.jpa.JPATransactionInterceptor.intercept(JPATransactionInterceptor.java:49)
    at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
    at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:53)
    at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:56)
    at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:53)
    at br.com.caelum.vraptor.interceptor.InterceptorListPriorToExecutionExtractor.intercept(InterceptorListPriorToExecutionExtractor.java:44)
    at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
    at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:53)
    at br.com.caelum.vraptor.interceptor.FlashInterceptor.intercept(FlashInterceptor.java:81)
    at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
    at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:53)
    at br.com.caelum.vraptor.interceptor.ResourceLookupInterceptor.intercept(ResourceLookupInterceptor.java:67)
    at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
    at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:53)
    at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:56)
    at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:53)
    at br.com.caelum.vraptor.core.DefaultRequestExecution.execute(DefaultRequestExecution.java:70)
    at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:92)
    at br.com.caelum.vraptor.ioc.spring.SpringProvider.provideForRequest(SpringProvider.java:56)
    at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:89)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:313)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)

    Está correto ou fiz algum coisa errada?

  2. Filipe Marques 28 Mar 2013 06:24

    Muito bom! Era o que eu estava procurando :D

    1. Tiago 25 Jul 2013 00:48

      Também curti! :)

  3. Ricardo Johannsen 26 Dez 2012 17:08

    Olá Washington muito bom o tutorial, só uma dúvida, seria possível ter em uma mesma jsp tanto o formulário de pesquisa quanto a listagem? e no caso de eu ter mais de um campo de busca como por exemplo buscar uma pessoa pelo seu cpf ou nome.como passo isso para o controller?

  4. Giovane Kauer 20 Ago 2012 23:53

    Gostaria de parabenizalo pelo blog, apenas estou sentindo uma pequena inatividade no mesmo...

    Agora quanto a minha duvida, criei os arquivos assim como demonstrou no exemplo, porém não está sendo gerada a tabela automaticamente. Gostaria de saber se tem mais alguma dica do motivo disto ocorrer.

  5. Hugo Chagas 3 Mai 2012 08:01

    Washington, primeiramente parabéns pelo blog. Fiquei com uma dúvida, nesses posts sobre o vraptor vc utiliza sempre o tomcat ? Pergunto isso, pois eu trabalho com o jboss e acho mais fácil usá-lo e tirar vantagem do hibernate/JPA já embutidos no AS, além de poder fazer uso do JTA. Como seria a configuração de um projeto com VRaptor + JPA no Jboss por exemplo?

  6. Adalto Jr. 13 Mar 2012 17:23

    Botelhos,
    - Se em minha aplicação por algum motivo eu queira acessar duas bases de dados diferentes, assim sendo, tenho que criar duas unidades de persistencia. Como eu faço pra trabalhar com elas sem perder as funcionalidades do VRaptor como injenção de dependencia etc.
    Como eu passo as duas via argumento? eu posso fazer isso?

    [ ]'s

    1. Washington Botelho autor 23 Mar 2012 12:11

      Oi Adalto,

      Neste caso creio que você teria de fazer um SessionFactory para poder trabalhar com uma ou com outra.
      Ainda não tenho um exemplo com tal implementação, mas pretendo fazer um post sobre para mostrar como trabalhar com JDBC de forma facilitada.

  7. Kleber Pinel 21 Nov 2011 08:29

    Antes de tudo, parabens pelo Post.. mt bom.

    Eu consegui fazer 100% do que você propos no post, mas agora estou tentando fazer a mesma coisa mas usando o servlet 3 (jee6) mas ai nao sei como configurar pra subir a JPA... eu até reabri um topico parecido que tinha no GUJ

    http://www.guj.com.br/java/196361-resolvido-vraptor-31--jee6--hibernate#1349459

    se puder me ajudar, agradeço.
    Obrigado

    1. Washington Botelho autor 26 Nov 2011 15:11

      Oi Kleber,

      Como esta usando Hibernate e não JPA, é necessário o hibernate.cfg.xml e não o persistence.xml.
      Sendo assim o Session será injetado em vez do EntityManager.

      Pessoalmente prefiro usar JPA, até porque a JPA 2 já tem Criteria, além disso, muito das vezes um HQL é mais simples de escrever e ender que um Criteria.

  8. Alexandre Coelho 10 Nov 2011 15:00

    Botelhos,

    Segui seu tutorial e criei a aplicação no Eclipse. Criei uma pasta META-INF dentro da pasta src e coloquei o persistence.xml dentro dessa pasta META-INF. Meu persistence-unit name é "default", conforme o padrão do Vraptor 3. Quando compilo, a seguinte mensagem de erro é exibida: Caused by: javax.persistence.PersistenceException: No Persistence provider for EntityManager named default.
    Não sei o que pode estar errado, pois coloquei o persistence.xml no local certo com o persistence-unit name com o nome correto.

    1. Washington Botelho autor 16 Nov 2011 14:49

      Oi Alexandre,

      Você registrou o provider da JPA no web.xml?

      1. Giovane Kauer 20 Ago 2012 23:51

        Tambem obtive este erro.
        Depois de algumas pesquisas descobri que faltava uma biblioteca do hibernate.

  9. Germano Fronza 28 Mar 2011 07:36

    Muito bom o seu artigo, parabéns.
    Queria te perguntar se você já fez deploy de uma aplicação assim no glassfish 3.1 (versão recentemente liberada).

    Quando a minha aplicação estava no glassfish 3.0.x eu não tinha problema com o JPATransactionInterceptor, mas agora nesta versão nova, a cada requisição para uma lógica do VRaptor eu vejo a exceção SQLException: Can't call commit when autocommit=true.

    Já setei o autocommit do hibernate (estou usando JPA 2) para false, mas ele ignora, pois "hibernate.connection.autocommit = false break the EJB3 specification".
    Estou a ponto de desabilitar logs de erro no hibernate, mas não é nada legal fazer isso... Thnks!

    1. Washington Botelho autor 1 Abr 2011 08:37

      Oi Germano,

      Infelizmente não sei te falar, pois nunca utilizei o Glassfish.
      Estranho ele não aceitar o autocommit = false.
      Tenta implementar o seu próprio OpenSessionInView para testar: http://vraptor.caelum.com.br/documentacao/interceptadores
      E desabilitar o log de longe não é algo legal.

  10. Amanda 5 Jan 2011 11:13

    Parabéns pelo blog.
    Adorei achar exemplos utilizando vRaptor, pois preciso fazer um projeto e não sei nem por onde começar.

    Muito bom seu post, mas ainda não ficou claro pra mim. Você diz criar isso ou configura aquilo, mas não sei onde fica cada um. Não sei como é a estrutura de um projeto com vRaptor.

    Por exemplo: criando o componente CustomProvider. Não sei onde tenho que criar esse componente.

    Estou meio perdida, ficaria grata se pudesse me ajudar.

    Obrigada!

    1. Washington Botelho autor 5 Jan 2011 19:03

      Oi Amanda,

      Criar manualmente o CustomProvider é algo que não precisamos fazer mais, porém você poderia deixá-lo em qualquer lugar da sua aplicação, já que o local do mesmo é indicado posteriormente no web.xml. Hoje basta registrar no web.xml o pacote do VRaptor que já tem esses provider:

      <context-param>
          <param-name>br.com.caelum.vraptor.packages</param-name>
          <param-value>br.com.caelum.vraptor.util.jpa</param-value>
      </context-param>
      

      Baixa o projeto exemplo aqui no Github pra você entender um pouco melhor. (:

  11. Danilo Akamine 19 Set 2010 18:01

    Muito bom o artigo, parabéns.

    Fiquei com a mesma dúvida do Epitácio Jr.
    Inicialmente também não achei essa resposta na documentação do VRaptor.

    Obrigado

    1. Washington Botelho autor 26 Set 2010 09:43

      Oi Danilo e Epitácio,

      Não existe isso de forma configurável e fácil no VRaptor, infelizmente.
      Para isso ser possível você precisaria implementar o seu próprio EntityManagerFactoryCreator.
      Acho que não vale o trabalho, sendo que seria mais fácil trocar o nome do seu persistence-unit.

  12. Epitácio Júnior 9 Set 2010 09:00

    Muito bom o artigo!

    era o que eu tava procurando para trabalhar com o VRaptor.

    Mas me surgiu uma dúvida, vc disse que por padrão o VRaptor procura o persistence-unit chamada default por padrão.

    Como faço pra alterar?

    Obrigado

  13. Washington Machado 20 Mai 2010 14:43

    Muito bom o artigo.

    Como estou começando ainda surgem algumas dúvidas bobas.

    O Hibernate é um framework de persistência ORM que surgiu antes mesmo da JPA, e quando esta surgiu o hibernate a implementou. Correto isso?

    Há também pessoas que utilizam o hibernate.cfg.xml ou hibernate.properties como arquivos de configuração. Quando eu trabalho com o persistence.xml significa que estou usando o hibernate como implementação da JPA e quando uso as outras configurações que sitei significa que estou usando o hibernate "isoladamente"? Ou não existe diferença alguma? (Não sei se defini muito bem) Há vantagens claras entre uma forma e outra de se trabalhar?

    Uma sugestão de post seria a criação de uma aplicação com o Flexigrid (baseado no seu comentario acima hehe).

    Obrigado! []s

    1. Washington Botelho autor 20 Mai 2010 18:11

      Fala Washington,

      O que você disse esta correto, o hibernate veio primeiro e hoje implementa a JPA.

      Sim, o persistence.xml é utilizado quando estamos utilizando JPA e o hibernate.cfg.xml quando estamos utilizando a implementação do Hibernate isoladamente.

      Até um tempo atrás eu defendia o Hibernate por ter a Criteria, porém hoje temo isso na JPA 2.0, apesar do Tomcat ainda não ter uma versão de suporte para a mesma. Mas ainda sim creio que utilizar a JPA é mais seguro por ser a especificação padrão e com isso nos garantir compatibilidade sempre.

      A aplicação com Flexigrid estava em rascunho, mas passei a bola para o meu amigo Makoto.
      Aprendi muito com ele e garanto que irá aprender também. (:

      Obrigado pelas leituras e feedback.

  14. Bruno 26 Mar 2010 12:41

    é uma ajuda também.
    gostaria de saber como fazer a parte visual com vRaptor, ele não tem aquelas tags do JSF com componentes visuais correto?
    Não sou design como faço o visual da minha aplicação com vRaptor
    É htlm puro?
    Como ganho produtividade com vRaptor?
    Uma sugestão é fazer uma serie de post sobre a integração de spring, jpa/hibernate e vraptor e Visão.

    []’s

    1. Washington Botelho autor 26 Mar 2010 13:57

      O VRaptor não tem os componentes visuais como JSF, porém você pode utilizar o jQueryUI. Outros componentes baseados no jQuery são achados aos montes na net, como o Flexigrid entre outros.

      A produtividade do VRaptor esta no código java, no trabalho com as camadas MVC através das convenções, injeção de dependência, casting automáticos, interceptors entre outras funcionalidades que estão melhores explicadas no site do VRaptor.

      A parte de JPA/Hibernate e Visão utilizando jQuery e algumas outras frameworks irão aparecer por aqui sempre. Apenas o Spring que por enquanto não terá exemplos por eu não estar trabalhando muito com ele.

      Obrigado pelo feedback Bruno.

  15. Bruno 24 Mar 2010 13:21

    O codigo fonte é livre??

    1. Washington Botelho autor 24 Mar 2010 14:58

      Infelizmente não, porém todas as funcionalidades do mesmo serão postadas e explicadas aqui no blog.

      Sugestões serão bem-vindas. (:

  16. Bruno 17 Mar 2010 13:10

    Cade a aplicação em vRaptor que voce disse no twiiter que ia colocar no ar??

    []'s

    1. Washington Botelho autor 17 Mar 2010 14:32

      Ainda esta em versão de teste.
      Com o passar do tempo irei implementando outras funcionalidades.

      http://www.moviecollection.com.br

  17. Junior 25 Fev 2010 08:10

    Muito bom tutorial me ajudo a fazer a migraçao aki na giran rsrsr vlww Kotó

  18. mauriciovoto 23 Fev 2010 23:24

    Parabéns pelo post !

    Qualquer divulgação de VRaptor integrado com outros frameworks é muito útil, pois ainda é um pouco difícil encontrar material de VRaptor 3 !

  19. Paulo Silveira 23 Fev 2010 14:55

    Excelente explicação, e, melhor ainda, passo a passo: antes criando o proprio EntityManagerUtil, e depois desacoplando para receber como argumento! Parabéns!

  20. Loiane 23 Fev 2010 11:56

    Massa o exemplo!
    E parabéns pelo blog!
    :)

Em resposta:
(cancelar)
Formate seu código utilizando Markdown.