17 de março de 2011

JPA 2.0: CRUD com EJB 3.1

Segundo a Oracle
"Enterprise JavaBeans (EJB) technology is the server-side component architecture for Java Platform, Enterprise Edition (Java EE). EJB technology enables rapid and simplified development of distributed, transactional, secure and portable applications based on Java technology."
Nessa postagem vamos criar um CRUD utilizando EJB como forma de injetar a conexão com o banco de dados. Atualmente está na versão 3.1, para ser utilizada com JEE 6. Eu já usei o EJB 3.0 com o JEE 5, e aquele era um "monstrinho" de pesado! Hoje nem chega ser perceptível o uso dele.
Bom... chega de teorias e vamos ao o que interessa,  crie um projeto com o nome CrudJPA, caso não saiba como iniciar o JPA no projeto, use esse projeto como base (siga os passos até criar o persistence.xml).
Em Pacotes de código-fonte crie os pacotes: model, controle e dao. Dentro do pacote model crie uma classe java com o nome de Cliente.java:
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="cliente")
public class Cliente implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="codigo")
    private Long codigo;

    @Column(length=100, name="nome")
    private String nome;

    public Long getCodigo() {
        return codigo;
    }

    public void setCodigo(Long codigo) {
        this.codigo = codigo;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (codigo != null ? codigo.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Cliente)) {
            return false;
        }
        Cliente other = (Cliente) object;
        if ((this.codigo == null && other.codigo != null) || (this.codigo != null && !this.codigo.equals(other.codigo))) {
            return false;
        }
        return true;
    }
}

Nosso modelo acima não tem nada de diferente, apenas as anotações do JPA que já foram faladas em postagens anteriores, antes que esqueçamos vamos mapear nossa entidade Cliente, dentro do persistence.xml, o mesmo deverá ficar assim:
<persistence version="2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/persistence" xsi:schemalocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="TesteJPAPU" transaction-type="JTA">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <jta-data-source>5tads</jta-data-source>
        <class>model.Cliente</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <properties>
            <property name="eclipselink.ddl-generation" value="create-tables" />
        </properties>
    </persistence-unit>
</persistence>

Agora dentro do pacote dao crie uma classe java com o nome de ClienteDAO.java, com o seguinte código:
package dao;

import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import model.Cliente;

/**
 *
 * @author andii
 */

@Stateless
public class ClienteDAO {

    @PersistenceContext(unitName="TesteJPAPU")
    private EntityManager em;

    public boolean gravar(Cliente cliente){
        boolean sucesso = false;
        try {
            em.merge(cliente);
            sucesso = true;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return sucesso;
    }

    public Cliente selecionar(Long codigo){
        Cliente cliente = null;
        try {
            cliente = em.find(Cliente.class, codigo);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return cliente;
    }

    public boolean remover(Cliente cliente){
        boolean sucesso = false;
        try {
            cliente = em.find(Cliente.class, cliente.getCodigo());
            em.remove(cliente);
            sucesso = true;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return sucesso;
    }

    public List<Cliente> listar() {
        List<Cliente> clientes = null;
        try {
            Query query = em.createQuery("Select c from Cliente c");
            clientes = query.getResultList();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return clientes;
    }
}

Agora vamos entender o que acontece no nosso ClienteDAO:
@Stateless: essa anotação define um bean de sessão sem estado, o que isso quer dizer? Que o nosso dao será criado toda vez que precisarmos dele. Caso precise que esse EJB se mantenha como um escopo de sessão mesmo, aí teria que usar @Stateful... mas não vem ao caso agora.
@PersistenceContext: este é usado para injetar o PU (persistence unit) em um EntityManager, veja que não é necessário instanciar um EntityManagerFactory. Lembre-se que esse TesteJPAPU é o mesmo que foi definido lá no meu persistence-unit do meu persistence.xml.
E quantos aos métodos, perceba nos códigos que não há mais a necessidade de abrir (usando em.getTransaction().begin()) e nem fechar (usando em.close()) a conexão com o banco de dados, ou seja, o EJB será o responsável por fazer isso (isso evita possíveis exception do tipo LazyInitializationException).
Apenas uma observação para o método gravar: veja que não tem o método alterar, isso pois o em.merge(cliente), funciona tanto para persistir um objeto como apenas para alterar, isso funciona assim: caso o JPA identifique que o objeto passado por parâmetro não está sendo gerenciado por ele, isso fará ele gravar um novo objeto, no caso aqui um novo cliente. Já quando você seleciona um cliente com um em.find(...) esse cliente está sendo gerenciado pelo JPA, e se mandar dar um em.merge(cliente) ele vai apenas alterar os dados, e não gravará um novo!

E agora vamos criar um bean dentro do pacote controle, para isso crie uma classe java com o nome de ClienteBean.java, dessa forma:
package controle;

import dao.ClienteDAO;
import java.io.Serializable;
import java.util.List;
import javax.ejb.EJB;
import javax.enterprise.context.SessionScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.inject.Named;
import model.Cliente;

/**
 *
 * @author andii
 */
@Named(value = "clienteBean")
@SessionScoped
public class ClienteBean implements Serializable {

    @EJB
    private ClienteDAO clienteDAO;
    private Cliente cliente = new Cliente();
    private List<Cliente> clientes;

    public void novo(){
        cliente = new Cliente();
    }

    public void gravar() {
        FacesContext context = FacesContext.getCurrentInstance();
        boolean resultado = clienteDAO.gravar(cliente);

        if (resultado) {
            cliente = new Cliente();
            context.addMessage(null, new FacesMessage("Cliente gravado com sucesso"));
        } else {
            context.addMessage(null, new FacesMessage("Falha ao gravar cliente!"));
        }
    }

    public void selecionar(ActionEvent evento) {
        Long codigo = (Long) evento.getComponent().getAttributes().get("codigo");
        cliente = clienteDAO.selecionar(codigo);
    }

    public void remover() {
        FacesContext context = FacesContext.getCurrentInstance();
        boolean resultado = clienteDAO.remover(cliente);

        if (resultado) {
            cliente = new Cliente();
            context.addMessage(null, new FacesMessage("Cliente removido com sucesso"));
        } else {
            context.addMessage(null, new FacesMessage("Falha ao remover cliente!"));
        }
    }

    //Getters e Setters
    public Cliente getCliente() {
        return cliente;
    }

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

    public List<Cliente> getClientes() {
        clientes = clienteDAO.listar();
        return clientes;
    }

    public void setClientes(List<Cliente> clientes) {
        this.clientes = clientes;
    }
}
A unica diferença nesse bean é o fato do nosso ClienteDAO estar anotado com um @EJB.
@EJB: Essa anotação serve para fazermos a injeção de dependência do nosso EJB, que neste caso é o ClienteDAO, por ele estar sendo injetado, em nenhum momento será necessário instância-lo com um new ClienteDAO();

Agora para finalizar de vez o CRUD, vamos alterar a página index.xhtml(que é criada juntamente com o projeto) e para não ter que criar outras páginas, fiz o CRUD inteiro somente na página index, mas aí é só fazer como desejar, esta deverá ficar assim:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h:form id="dadosCliente">
            <fieldset style="width: 350px">
                <legend>Novo cliente</legend>
                <h:commandButton value="Novo" action="#{clienteBean.novo}" />
            </fieldset>
            <fieldset style="width: 350px">
                <legend>Dados do cliente</legend>
                <h:panelGrid columns="4">
                    <h:outputText value="Nome" />
                    <h:inputText value="#{clienteBean.cliente.nome}" />
                    <h:commandButton value="Gravar" action="#{clienteBean.gravar}" />
                    <h:commandButton value="Remover" action="#{clienteBean.remover}" rendered="#{clienteBean.cliente.codigo > 0}" />
                </h:panelGrid>
            </fieldset>
        </h:form>

        <h:form>
            <fieldset style="width: 350px">
                <legend>Clientes</legend>
                <h:dataTable value="#{clienteBean.clientes}" var="cliente" border="1">
                    <h:column>
                        <f:facet name="header"><h:outputText value="CODIGO" /></f:facet>
                        <h:outputText value="#{cliente.codigo}" />
                    </h:column>
                    <h:column>
                        <f:facet name="header"><h:outputText value="NOME" /></f:facet>
                        <h:outputText value="#{cliente.nome}" />
                    </h:column>
                    <h:column>
                        <h:commandButton value="Selecionar" actionListener="#{clienteBean.selecionar}">
                            <f:attribute name="codigo" value="#{cliente.codigo}" />
                            <f:ajax render=":dadosCliente" execute="@this" />
                        </h:commandButton>
                    </h:column>
                </h:dataTable>
            </fieldset>
        </h:form>
    </h:body>
</html>
Inicialmente essa seria a visualização da página index:



Ao clicar no botão Selecionar o cliente será selecionado, e será renderizado um botão de remover, enfim quando selecionado um cliente, a janela fica assim:


Mas como eu falei, essa é apenas uma opção de como pode ser feito nossa visualização, use sua criatividade para deixá-la como achar melhor!

Para mais informações veja a API do Java EE 6, para ver mais sobre o EJB, selecione o pacote javax.ejb. Aqui finalizamos mais uma postagem... fiz ela bem simples, para dar mais foco ao EJB 3.1 com o JPA 2.0. :)

31 comentários:

  1. Bacana o post, parabéns, obrigado por compartilhar seu conhecimento, mas tenho uma dúvida a classe ClienteDAO não está capturando as exceções causadas pelo jpa, voçê fez alguma configuração no servidor pra isso funcionar?

    ResponderExcluir
  2. Obrigada! Quanto a captura de exceções do JPA, não fiz nada não, até já tentei capturar elas com try catch mas por algum motivo desconhecido não consegui, por isso trato elas de modo geral com o Exception.

    ResponderExcluir
  3. Obrigado por responder

    então mas nem desse jeito que você fez não está funcionando no meu servidor de Aplicação

    public boolean gravar(Cliente cliente){ boolean sucesso = false;
    try {
    em.merge(cliente);
    sucesso = true;
    } catch (Exception e) { e.printStackTrace();
    }
    return sucesso;
    }

    só consigo capturar desse jeito se eu colocar um "em.flush()" depois do "em.merge()", qual servidor esta usando?

    ResponderExcluir
  4. Eu uso o GlassFish... mas que tipo de exceção estaria dando?

    ResponderExcluir
  5. não está dando nenhuma exceção, mas eu gostaria de capturar quando ocorrer, tipo o cliente tenta excluir um registro de uma tabela que está ligado com outra tabela

    ResponderExcluir
  6. Ah sim, se eu não me engano até tentei fazer isso, mas acabei não conseguindo, talvez por falta de conhecimento da minha parte.

    ResponderExcluir
  7. Excelente post! É possível encapsular as exceções utilizando aspecto e gerar um arquivo de log. ;) Abraço.

    ResponderExcluir
  8. Oi Andi, gosto muito do seu blog, me ajuda bastante!

    Queria saber, teria alguma diferença se vc colocasse a anotação do SINGLETON, ou so agregaria, enfim, mudaria alguma coisa neste exemplo!

    Ajudinhaa, obrigada

    ResponderExcluir
  9. Olá go_utfpr! em que classe vc diz de colocar o @Singleton? Se for no DAO, seria interessante usar o @Statefull para manter ele construído durante a aplicação. Se for no Bean, não teria porque fazer isso, já que o @SessionScoped já tem a função de manter o estado do bean

    ResponderExcluir
  10. Brunetta, ajude me por favor,antes que eu tenha um ataque epiléptico e vomite meu cérebro de tanta raiva.

    Como fica esse arquivo persistence.xml quando as tabelas do banco já estão criadas? Do jeito que está "create-tables" ele fica tentando criar tabelas que já existem.Que tipo de propriedade devemos setar?

    Abraços.

    ResponderExcluir
  11. Olá Helcio, já tentou tirar essa propriedade? ou talvez usar como "update"? [lembro que o update funcionava com o hibernate, nunca testei no eclipselink] ... alem disso aconselho fortemente que leia isso aqui: http://wiki.eclipse.org/Using_EclipseLink_JPA_Extensions_%28ELUG%29#Using_EclipseLink_JPA_Extensions_for_Schema_Generation

    ResponderExcluir
  12. Excelente post, mas eu não concordo com uma coisa.
    Com @SessionScoped o bean Clientebean mesmo não sendo usado/acessado ficará ativo e consumindo recursos.
    Para este seu modelo acredito que seja melhor usar o @RequestScoped, pois vc esta dando new nos atributos na declaração.
    Ou até o @ConversationScoped já que vc está usando CDI (algo parecido seria tirar o bean da sessão usando o facesContext mas não funciona com o javax.enterprise.context.SessionScoped;).

    ResponderExcluir
  13. Olá matusmts! Com certeza que não se deve colocar o @SessionScoped em todos beans de um projeto. Em nenhum eu afirmei que deveria usar o @SessionScoped, pois o foco da postagem é o EJB e o JPA, e qualquer escopo que utilizar aí vai funcionar o que eu quis mostrar.

    ResponderExcluir
  14. Entendi andii.brunetta, concordo com vc.
    É que no momento estou bem enrrolado tentando entender os scopos do CDI, e o funcionamento dos EJBs.
    Seu blog está otimo, já está no google-reader.
    vlw :D

    ResponderExcluir
  15. Vc teria um projeto pronto de Cliente, Produto, Venda. Em JPA 2.0, com interface gráfica sem ser web?

    ResponderExcluir
    Respostas
    1. Em Desktop não terei nada feito, mas com este exemplo http://javasemcafe.blogspot.com.br/2011/02/jpa-20-criando-tabelas-parte-1.html vc consegue usar em Desktop, pois o EJB é apenas para Web.

      Excluir
  16. Muito Show cara...Valeu!
    funcionou certinho, fiz com Postgresql.
    Seu blog tá ajundando demais...parabens!

    ResponderExcluir
    Respostas
    1. Que bom! Fico feliz por estar ajudando :)

      Excluir
  17. Boa tarde, eu estou tentando adicionar um campo idade porém gera o seguinte erro: IDADE' não é uma coluna na tabela ou VTI 'ADMIN.CLIENTE' como eu faço para adicionar esta coluna?

    ps: meu banco é nativo do netbeans.

    ResponderExcluir
  18. Estava analisando seu código... ainda sou meio iniciante, tudo funcionou normal, o CRUD funciona perfeitamente, porém não entendi para que serve aquele botão de Novo ele está chamando o construtor certo? Aqui para mim ele não faz nada mais do que limpar os campos.

    ResponderExcluir
    Respostas
    1. a função dele é inicializar um novo objeto, e consequentemente limpar os campos

      Excluir
  19. Olá, ao visualizar a Tela dar para ver que você está usando JSF, então a Classe de Controle ClienteBean é um ManagedBean. você fez o mapeamento via xml? pois não há a anotação @ManagedBean na Classe ClienteBean.

    O que dar para entender é que o EJB "gerencia" o modelo e persistência da aplicação é isso?

    ResponderExcluir
    Respostas
    1. Olá Projeto Nutrilline, nesse caso eu usei o @Named que tem a mesma funação do @ManagedBean, porém são de pacotes diferentes. Quando eu fiz esse tutorial eu usava o @Named, pois não estava me acertando em usar o @ManagedBean, mas pode usar o @ManagedBean que funciona da mesma forma. Quanto ao EJB, ele gerencia a conexão com o BD, pois vc pode ver que em nenhum momento eu abro ou fecho a conexão com o banco, é ele que faz, quanto a gerenciar o modelo e a persistência, isso é feito pelo JPA.

      Excluir
  20. Ola td bem?
    Por favor disponibilize pra download.
    Obrigado.

    ResponderExcluir
  21. OLa..Alguem poderia mim ajudar a corrigir os bugs deste crud ?

    ResponderExcluir
  22. aqui a Quest: https://pt.stackoverflow.com/questions/241465/erro-target-unreachable-identifier-clientebean-resolved-to-null

    ResponderExcluir
  23. Não esta realizando a listagem e nem o cadastro!

    ResponderExcluir

Deixe seu comentário... ;)