quarta-feira, 13 de fevereiro de 2008

EJB3 + DAO

Muito se discute na comunidade se devemos usar um mecanismo de persistência com EJB3, pois o container EJB somente irá injetar o EntityManger nas classes(Servlets e EJB) que são gerenciadas pelo container web, ou seja, isso é um problema pois o nosso DAO não é gerenciado pelo container web, e ele com certeza precisa do EntityManager.

Esse problema não existia anteriormente porque o nosso DAO era responsável por criar a conexão com o banco de dados, o que nos trazia mais problemas, pois tínhamos a questão da transação, que não podia ser gerenciada pelo DAO, apesar deste criar a conexão com o banco de dados.

Outro ponto negativo do EJB3 + DAO é que o nosso DAO não é algo mais complexo, portanto fica simples vc inserir um registro no banco de dados com poucas linhas:

@PersistenceContext
private EntityManager em;

public void persist(Object o){
em.persist(o);
}

Pois é, isso mesmo, o código acima irá gravar qualquer entidade no banco de dados, vc pode colocar essa função no seu EJB e "eureka" resolvido o nosso mecanismo de persistência.

Mas nem tudo é flores, essa abordagem não serve para nós, pois não estamos fazendo um sistema para a padaria da sua esquina, ou para lojinha do seu tio.

O nosso sistema não pode simplismente gravar a entidade no banco de dados e esperar que tudo ocorra normalmente, o método acima pode precisar de uma transação para duas entidades diferentes, ou pior, pode lançar uma exceção do banco de dados para o cliente. Imagina o seu EJB tratando as exceções do banco de dados mais as de negócio do EJB, não vai ficar muito elegante né :-/

Portanto aconselho sim, que vc use um mecanismo de persistência com o seu EJB, e vou demonstrar abaixo como vc pode fazer isso.

Primeiro vamos criar a nossa fabrica de DAO que será responsável por injetar o nosso EntityManager em nossos DAOs. Esta fábrica é um EJB local pois não queremos que clientes fora do nosso container web façam uso dessa classe.
Segue abaixo a interface da nossa fábrica.

@Local
public interface RepositorySession {
@TransactionAttribute(TransactionAttributeType.NEVER)
public LoginRepository getLoginRepository();

@TransactionAttribute(TransactionAttributeType.NEVER)
public TimesheetRepository getTimesheetRepository();
}

Agora segue abaixo a nossa implementação da fábrica.

@Stateless
public class RepositorySessionImpl implements RepositorySession{
@PersistenceContext
private EntityManager em;

@TransactionAttribute(TransactionAttributeType.NEVER)
public LoginRepository getLoginRepository(){
return new LoginDAO().setEntityManager(em);
}

@TransactionAttribute(TransactionAttributeType.NEVER)
public TimesheetRepository getTimesheetRepository(){
return new TimesheetDAO().setEntityManager(em);
}
}

Vc precisa reparar em três anotações importantes:
@Stateless significa que não vamos manter um estado de conversação para um cliente particular. Quando um cliente invoca o método de um bean stateless, as variáveis do bean podem conter um estado, mas somente para a duração da chamada. Ao terminar a execução, o estado é encerrado. Exceto durante a chamada do método, todas as instâncias de um bean stateless são equivalentes, permitindo que o container EJB atribua uma instância a qualquer cliente

@PersistenceContext significa que esta variável deve ser injetada com o EntityManager através do container web.
O PersistenceContext é um conjunto de objetos/entidades gerenciadas por um EntityManager. Objetos que representam entidades serão recuperados, inseridos ou removidos de um banco de dados pelo EntityManager. Estas entidades estarão anexadas ao contexto de persistência o qual será fechado/removido quando o EntityManager for
fechado. Se o contexto for mantido ativo porém o objeto/entidade for desanexada do contexto, então esta entidade não mais será gerenciado pelo EntityManager e modificações na sua instância não serão refletidos no banco de dados

@TransactionAttribute (TransactionAttributeType.NEVER)
Isto significa que o nosso método não irá iniciar uma transação para instanciar as classes, pois não faz sentido já que estamos apenas retornando um objeto sem fazer qualquer alteração no banco de dados.
Agora que vc já sabe como injetar EntityManager em nossos DAOs, vamos fazer uso deles em nossos EJB, segue abaixo um exemplo:

@Local
public interface TimesheetFacade {

@PostConstruct
public void initialize();

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void persist(Timesheet Timesheet);
}

Veja que eu anotei a minha interface como @Local, mas vc poderia anotar com @Remote pois esta classe pode ser usada por clientes externos, pois esta classe contem toda a nossa lógica de negocio, validações e controle das exceções e não tem problema em disponibilizar ela fora do nosso container. Agora segue abaixo a nossa implementação:

@Stateless
public class TimesheetFacadeImpl implements TimesheetFacade {
@EJB
RepositorySession repository;
TimesheetRepository timesheetRepository;

@PostConstruct
public void initialize() {
timesheetRepository = repository.getTimesheetRepository();
}

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void persist(Timesheet timesheet) {
timesheetRepository.persist(timesheet);
}
}

Precisamos prestar atenção para 3 anotações:
@EJB isso irá fazer como que a nossa variável receba a nossa fábrica de DAOs

@PostConstruct aqui criamos uma instância para o nosso repositório, não podemos colocar isso no construtor da classe pois precisamos esperar a injeção da nossa fábrica.

@TransactionAttribute(TransactionAttributeType.REQUIRED) Agora sim vamos usar uma transação, simples assim, nada de XML, apenas anotamos para que o container web saiba que aqui ele precisa iniciar uma transação.

Pronto, acabamos aqui, vc deve se perguntar: Cade a tal da validação, tratamento de exceção, lógica de negocio e etc, pois bem... deixamos isso para o próximo artigo pois vamos descrever como vc deve usar os seus EJB, e como configurar a sua aplicação.

6 comentários:

Carlos disse...

Fazendo desta forma, a fábrica de DAOs fica sendo um EJB (Stateless), e assim sendo pode ser acessada de qualquer lugar via jndi/rmi.
Ou seja, o encapsulamento/componentização foi quebrado.
Alguém pode querer quebrar a arquitetura direto da camada de apresentação por exemplo sem passar pelas validações de negócio necessárias!

RonildoJunior disse...

Tem razão :-/

Caro leitor, se vc esta lendo esse artigo e esta implementando essa solução, deve ter ciência que a arquitetura acima tem uma falha o qual eu não tinha percebido. Apesar do EJB ser local um cliente interno do container pode acessar a fábrica quebrando o encapsulamento.

Estou procurando uma solução melhor para injetar o EntityManager no DAO pois agora não tenho nada em mente, peço que aguarde. Obrigado.

RonildoJunior disse...

Podemos resolver a questão de duas formas:
Primeira opção - Apagar a fábrica e fazer a injeção do EntityManager no metodo initialize do nosso EJB. O problema disso é que ficará claro que estamos usando um DAO e nosso conceito de repositório irá por agua abaixo

Segunda opção - No método initialize vamos usar uma fábrica com métodos estáticos e que recebem um entityManager como argumento.
Nessa segunda abordagem, deixamos de lado a nossa fábrica como sendo um EJB. Ela é agora apenas uma fábrica que recebe o EntityManager através de um parametro por uma função estática e retorna para nós o nosso repositorio e não um DAO. Segue código abaixo:

@PersistenceContext
private EntityManager em;

@PostConstruct
public void initialize() {
timesheetRepository = RepositoryFactory.getTimesheetRepository(em);
}

Segue abaixo nossa fábrica
public static TimesheetRepository getTimesheetRepository(EntityManager em){
return new TimesheetDAO().setEntityManager(em);
}

Veja que eu não escolhi a primeira solução porque a fábrica tinha como função injetar o nosso entityManager e também esconder os nossos DAOs, pois ela retorna apenas os repositórios.

Roberto disse...

Olá,

Podemos resolver de outra maneira tbm... não usar EJB... hehe... Por mais que o uso de anotações tenham melhorado e facilitado o uso de EJB, ainda prefiro não utilizá-los, EJB é uma tecnologia que me deixou traumatizado...

RonildoJunior disse...

Roberto
Se vc pensa assim... então vc pode trabalhar com os artigos anteriores pois estes não usam EJB3 ;)

Acid_Phreak disse...

Existem anotações do Java e do JBOSS que permitem efectuar alguma segurança nos EJB.

ex:
@SecurityDomain("xxxxx"),
@RolesAllowed( { "role1","role2"})

In: http://schuchert.wikispaces.com/EJB3+Tutorial+6+-+Security