[JavaEE 6] EJB 3.1 no GlassFish V3 e NetBeans

segunda-feira, 15 de setembro de 2008


Esta semana saiu nas bancas a edição 31 da revista Mundo Java com o título "Futuro do Java Corporativo", com um artigo meu sobre o mesmo tema.
Para quem quer saber o que vai rolar na próxima especificação do Java EE 6, está edição está um prato cheio, portanto compre uma edição e aproveite...
Não vou entrar em muitos detalhes da matéria, mas vou descrever aqui melhor a demo que está na revista, onde apresento uma aplicação utilizando Session Bean da especificação de EJB 3.1 no GlassFish V3.

Instalando o GlassFish V3 e o conteiner EJB

No site do GlassFish baixe o arquivo zip e descompacte em um diretório da sua escolha, a partir deste momento vamos chamar apenas de GLASSFISH_HOME\bin e execute o updatetool (Figura 1).
Na opção available addons, selecione glassfishv3-ejb e clique em Install, esta ação irá instalar o container EJB no Glassfish.

Instalando o plugin GlassFish V3 no NetBeans

Agora inicie o NetBeans (de preferência a versão 6.1 ou 6.5) e instale o plugin do GlassFish V3 para o NetBeans em Ferramentas | Plugins, selecione em plugins disponíveis "Glassfish JavaEE Integration" e clique em instalar.
Após instaldo o plugin, na aba Serviços, adicione um novo servidor, selecione a opção GlassFish V3 e siga os passos apontando o diretório GLASSFISH_HOME de instalação e finalizar.



Vamos explorar agora uma funcionalidade no NetBeans para a criação de CRUD, crie uma nova Aplicação Web na categoria Web e clique em Próximo.
Neste passo digite "DEMO_EJB31" e clique em Próximo, na opção servidores selecione o servidor GlassFish V3 T2 recém instalado e clique em Próximo. No último passo selecione o framework JavaServer Faces e clique em Finalizar.

Agora vamos criar uma aplicação CRUD completa baseada em uma entidade de banco de dados, para isso clique em Arquivo | Novo Arquivo, na categoria Persistence selecione a opção "Classes de entidade do banco de dados" e clique em Próximo.
OBS: Antes de fazer o passo abaixo, inicie o banco de dados Derby na guia Serviços do NetBeans, clique com o botão direito do mouse em Java DB e clique em "Iniciar Servidor".

Neste segundo passo, na lista Fonte de Dados selecione a opção "Nova Fonte de Dados", no campo "Nome JNDI" digite jndi/TesteEJB e na lista "Conexão de banco de dados" selecione o banco "jdbc:derby://localhost:1527/vir" já existente e clique em OK.

Se tudo ocorreu de maneira correta será apresentada uma lista de tabelas disponíveis. Selecione a tabela Employee, clique em Adicionar e em seguida clique em Próximo.

No campo nome do pacote digite br.com.netfeijao.entities e por fim clique no botão "Criar unidade de persistência". Mantenha os valores default clique em Criar e depois clique em Finalizar. Esta ação irá criar a classe persistente Employee no pacote informado.

Agora vamos utilizar um recurso no NetBeans para a criação de um CRUD com páginas JSF baseado em entidades JPA, no caso a classe Employee que acabamos de criar. Acesse o menu Arquivo | Novo Arquivo, na pasta categoria Persistence selecione "Páginas JSF de classes de entidade" e clique em próximo. Adicione a única classe persistente (Employee) existente (ver figura 1), clique em Próximo e a seguir clique em Finalizar.
Execute a aplicação pressionando o botão F6 e faça testes, perceba que o NetBeans criou uma aplicação completa "a lá Ruby on Rails" com apenas alguns cliques.

public class EmployeeController {
private Employee employee = null;
private List employees = null;
@Resource
private UserTransaction utx = null;
@PersistenceUnit(unitName = "DEMO_EJB31PU")
private EntityManagerFactory emf = null;

public EntityManager getEntityManager() {
return emf.createEntityManager();
}
public int batchSize = 5;
private int firstItem = 0;
private int itemCount = -1;

public SelectItem[] getEmployeesAvailableSelectMany() {
return getEmployeesAvailable(false);
}

public SelectItem[] getEmployeesAvailableSelectOne() {
return getEmployeesAvailable(true);
}

private SelectItem[] getEmployeesAvailable(boolean one) {
List allEmployees = getEmployees(true);
int size = one ? allEmployees.size() + 1 : allEmployees.size();
SelectItem[] items = new SelectItem[size];
int i = 0;
if (one) {
items[0] = new SelectItem("", "---");
i++;
}
for (Employee x : allEmployees) {
items[i++] = new SelectItem(x, x.toString());
}
return items;
}

public Employee getEmployee() {
if (employee == null) {
employee = getEmployeeFromRequest();
}
if (employee == null) {
employee = new Employee();
}
return employee;
}

public String listSetup() {
reset(true);
return "employee_list";
}

public String createSetup() {
reset(false);
employee = new Employee();
return "employee_create";
}

public String create() {
EntityManager em = getEntityManager();
try {
utx.begin();
em.persist(employee);
utx.commit();
addSuccessMessage("Employee was successfully created.");
} catch (Exception ex) {
try {
if (findEmployee(employee.getId()) != null) {
addErrorMessage("Employee " + employee + " already exists.");
} else {
ensureAddErrorMessage(ex, "A persistence error occurred.");
}
utx.rollback();
} catch (Exception e) {
ensureAddErrorMessage(e, "An error occurred attempting to roll back the transaction.");
}
return null;
} finally {
em.close();
}
return listSetup();
}

public String detailSetup() {
return scalarSetup("employee_detail");
}

public String editSetup() {
return scalarSetup("employee_edit");
}
..
}
Listagem 1 - Parte da Classe EmployeeController gerado pelo NetBeans

Perceba que o NetBeans gerou o código de persistencia em uma classe controller, vamos melhorar um pouco isto, tirando o código de acesso aos dados e isolando isto em uma classe DAO, depois vamos transformar esta classe em um EJB sem interface,

OBS> Cuidado, o uso de um EJB DAO deve ser restrito, pois dependendo do seu uso, isso pode ser um Anti-Pattern

Vamos criar uma nova classe Java acessando o menu Arquivo | Novo Arquivo. Nomeie a classe como EmployeeDAO no pacote br.com.mundojava.dao. Nesta classe vamos colocar todos os métodos de acesso ao banco utilizados na classe EmployeeController e inclui-los na classe recém criada. Veja parte do código na Listagem 18.

Vamos criar uma nova classe Java acessando o menu Arquivo | Novo Arquivo. Nomeie a classe como EmployeeDAO no pacote br.com.mundojava.dao.
Adicionalmente vamos criar uma classe para tratamento de Exceptions, crie uma classe e nomeie de DatabaseException, na classe DAO vamos encapsular os erros de acesso a banco nesta classe.

Criada a classe DAO, vamos colocar todos os métodos de acesso ao banco utilizados na classe EmployeeController. Feito isto, transforme esta classe EmployeeDAO em um EJB colocando a anotação Stateless em cima da declaração da classe. Veja parte da classe criada na Listagem 2.

@Stateless
public class EmployeeDAO {

public static final int PROCESSADO = 1;
public static final int JA_EXISTE = 2;
public static final int ERRO = 3;

@PersistenceUnit(unitName = "DEMO_EJB31PU")
private EntityManagerFactory emf;

private EntityManager getEntityManager() {
  return emf.createEntityManager();
}

  public Employee getEmployeeFromRequestParam(Object employee) {
    EntityManager em = getEntityManager();
     try{
       Employee o = em.merge((Employee) employee);
       return o;
     } finally {
       em.close();
     }
  } 

  public int create(Employee employee) throws DatabaseException{
    EntityManager em = getEntityManager();
    try {
      em.getTransaction().begin();
      em.persist(employee);
      em.getTransaction().commit();
      return EmployeeDAO.PROCESSADO;
    } catch (Exception ex) {
      try{
       int opt = 0;
       if (getEmployee(employee.getId()) != null) {
         opt = EmployeeDAO.JA_EXISTE;
       } else {
         opt = EmployeeDAO.ERRO;
         throw new DatabaseException("A persistence error occurred.");
       }
       em.getTransaction().rollback();
       return opt;
      }catch(Exception sup){
        throw new DatabaseException("An error occurred attempting to roll back the transaction.");
      } finally {
        em.close();
      }
    }
  }

public void edit(Employee employee) throws DatabaseException {
EntityManager em = getEntityManager();
try {
em.getTransaction().begin();
em.merge(employee);
em.getTransaction().commit();
} catch (Exception ex) {
try {
em.getTransaction().rollback();
throw new DatabaseException(ex.getLocalizedMessage());
} catch (Exception e) {
throw new DatabaseException("An error occurred attempting to roll back the transaction.");
}
} finally {
em.close();
}
}
...
}
Listagem 2 - Parte da classe EmployeeDAO recém criada.

Fica um desafio para o leitor do blog, criar os métodos
public void destroy(Employee employee);
public List getEmployees(boolean all, 
                         int batchSize, 
                         int firstItem); 
public List getEmployees(boolean all,  
                         int batchSize,  
                         int firstItem); 
public Employee getEmployee(Integer id); 
public int getItemCount();
Por fim, na classe EmployeeController vamos fazer algumas alterações para consumir o EJB sem interface. Primeiro declare uma variável do tipo EmployeeDAO, e vamos injetar com a referência do EJB, e troque todas as referências ao código de acesso ao banco para apontar para o nosso EJB DAO, veja como ficou na classe EmployeeController na Listagem 3.

public class EmployeeController {
private Employee employee = null;
private List employees = null;
public int batchSize = 5;
private int firstItem = 0;
private int itemCount = -1;
@EJB
EmployeeDAO dao;

public SelectItem[] getEmployeesAvailableSelectMany() {
return getEmployeesAvailable(false);
}

public SelectItem[] getEmployeesAvailableSelectOne() {
return getEmployeesAvailable(true);
}

private SelectItem[] getEmployeesAvailable(boolean one) {
List allEmployees = getEmployees(true);
int size = one ? allEmployees.size() + 1 : allEmployees.size();
SelectItem[] items = new SelectItem[size];
int i = 0;
if (one) {
items[0] = new SelectItem("", "---");
i++;
}
for (Employee x : allEmployees) {
items[i++] = new SelectItem(x, x.toString());
}
return items;
}

public Employee getEmployee() {
if (employee == null) {
employee = getEmployeeFromRequest();
}
if (employee == null) {
employee = new Employee();
}
return employee;
}

public String listSetup() {
reset(true);
return "employee_list";
}

public String createSetup() {
reset(false);
employee = new Employee();
return "employee_create";
}

public String create() {
int resultado = dao.PROCESSADO;
try {
resultado = dao.create(employee);
if (resultado == dao.PROCESSADO) {
addSuccessMessage("Employee was successfully created.");
} else if (resultado == dao.JA_EXISTE) {
addErrorMessage("Employee " + employee + " already exists.");
}
} catch (Exception ex) {
ensureAddErrorMessage(ex, ex.getLocalizedMessage());
}
return listSetup();
}

public String detailSetup() {
return scalarSetup("employee_detail");
}

public String editSetup() {
return scalarSetup("employee_edit");
}

private String scalarSetup(String destination) {
reset(false);
employee = getEmployeeFromRequest();
if (employee == null) {
String requestEmployeeString = getRequestParameter("jsfcrud.currentEmployee");
addErrorMessage("The employee with id " + requestEmployeeString + " no longer exists.");
String relatedControllerOutcome = relatedControllerOutcome();
if (relatedControllerOutcome != null) {
return relatedControllerOutcome;
}
return listSetup();
}
return destination;
}

public String edit() {
EmployeeConverter converter = new EmployeeConverter();
String employeeString = converter.getAsString(FacesContext.getCurrentInstance(), null, employee);
String currentEmployeeString = getRequestParameter("jsfcrud.currentEmployee");
if (employeeString == null || employeeString.length() == 0 || !employeeString.equals(currentEmployeeString)) {
String outcome = editSetup();
if ("employee_edit".equals(outcome)) {
addErrorMessage("Could not edit employee. Try again.");
}
return outcome;
}
try {
dao.edit(employee);
addSuccessMessage("Employee was successfully updated.");
} catch (DatabaseException ex) {
String msg = ex.getLocalizedMessage();
if (msg != null && msg.length() > 0) {
addErrorMessage(msg);
}else if (getEmployeeFromRequest() == null) {
addErrorMessage("The employee with id " + currentEmployeeString + " no longer exists.");
return listSetup();
} else {
addErrorMessage("A persistence error occurred.");
}
return null;
}
return detailSetup();
}

public String destroy() {
employee = getEmployeeFromRequest();
if (employee == null) {
String currentEmployeeString = getRequestParameter("jsfcrud.currentEmployee");
addErrorMessage("The employee with id " + currentEmployeeString + " no longer exists.");
String relatedControllerOutcome = relatedControllerOutcome();
if (relatedControllerOutcome != null) {
return relatedControllerOutcome;
}
return listSetup();
}
try {
dao.destroy(employee);
addSuccessMessage("Employee was successfully deleted.");
} catch (DatabaseException ex) {
ensureAddErrorMessage(ex, ex.getLocalizedMessage());
return null;
}

String relatedControllerOutcome = relatedControllerOutcome();
if (relatedControllerOutcome != null) {
return relatedControllerOutcome;
}
return listSetup();
}
...
}

Listagem 3 - Classe EmployeeController refatorada para utilizar o EJB DAO sem interface

Perceba que na expressão "@EJB EmployeeDAO dao" estamos referenciando ao EJB, sem interface para atrapalhar, claro que não é possível criar uma instância utilizando a palavra reservada new ainda, na verdade estamos trabalhando com um proxy, mas se quisermos podemos fazer um lookup utilizando JNDI também.
Vou deixar o restante dos métodos para o leitor resolver, é apenas trocar a referencia pela classe dao conforme os métodos acima.


Compile e faça o deploy, rode a aplicação, se tudo deu certo você irá ver a sua aplicação rodando (ver figura 2) com um EJB sem interface, e o melhor, o EJB está dentro de um arquivo .war, já estamos implementando o empacotamento simplificado.






Veja como ficou o empacotamento do nosso projeto na Figura 3.



Para maiores informações sobre o futuro do Java Corporativo, leia a edição 31 da Mundo Java, que ainda traz ótimos artigos como:

  • EJB 3.1:Conheça as Novidades do Futuro do Java Corporativo.
  • Autor:Wagner Roberto dos Santos
  • Grizzly e Comet - Ajax Reverso com Escalabilidade.
  • Autor: Pedro Cavalero
  • Usando o Mavem para melhorar a Qualidade dos seus Projetos.
  • Autor:Márcio Varchavsky
  • Criando Software mais próximo do Cliente com Domain-Drivgen Design.
  • Autor:Sérgio Lopes
  • Setembro: Mês de Java.
  • Autor:Mauricio Leal
  • Testes de unidades Avançadas com JMock 2
  • Autor:Eduardo Guerra
  • Gerenciamento de Conteúdo Web com OpemCMS -Customização de Sites.
  • Autor:Rodrigo Cunha de Paiva
  • Tirando o Máximo dos Interceptors no Struts2.
  • Autor: José Yoshiriro Ajisaka Ramos
  • Tendências em Foco:Ganhando com Open Source
  • Autor:Cezar Taurion
  • Jogo Rápido
  • Autor:Charbel Symanski e Rodrigo Barbosa Cesar
  • Mundo OO: Requisitos Executáveis com FIT
  • Autor:Rodrigo Yoshima
  • SOA na Pratica:Iniciando Projetos SOA.
  • Autor:Ricardo Ferreira









Diversão Garantida !!!

2 comentários:

Simeão disse...

Muito bom o artigo, mas fiquei com uma dúvida. Por que, depois que se criou a classe DAO, não mais se usou JTA?

Wagner Santos disse...

Olá Simeão,

Na época que fiz esta demo, foi bem quando foi lançado o suporte a EJB 3.1 no Glassfish V3,

E nesta época estava dando muito pau utilizar JTA, na época conversei até com o Reza Rahman e Arun Gupta sobre isto, acredito que hoje este problema já tenha sido resolvido,

Se você quiser testar, fica aí o desafio =)

é só incluir
@Resource UserTransaction usr;

e trocar o código de transação do código por usr.begin(), usr.commit() e usr.rollback();

Qualquer coisa é só avisar !!!

Abraço,