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.

quinta-feira, 7 de fevereiro de 2008

Novos horizontes

Devido a uma série de alterações no meu trabalho, volto somente agora a postar novos artigos.
A boa noticia é que estou refatorando o ITrust e devo escrever aqui as alterações que eu fiz e como isso pode lhe ajudar.

Eu mudei o servidor de aplicação, pois vamos trabalhar com EJB3, e isso sinceramente é muito bom.
Uma das vantagens de se usar EJB3 é que agora não precisamos mais controlar as transações, pois agora o container web vai fazer isso.
Vamos continuar usando Hibernate, porem não existe mais xml, vamos usar annotions ;-)
Basicamente vamos jogar muita coisa fora, pois até mesmo a abordagem do ITrust foi alterada.

Não pretendo mais fazer diretamente um ERP, mas sim um timesheet. É bem mais simples começar esse projeto fazendo um timesheet, pois a partir disso podemos posteriormente adicionar um controle financeiro, ou seja, vamos acoplar os módulos com o passar do tempo, mas o foco agora é fazer esse timesheet e como isso não é muito complicado eu já fiz uma grande parte.

Bom, chega de papo e vamos ao código.
Começando pelo mecanismo de persistência, vamos alterar um pouco nossas classes e também alguns conceitos que são o mais importante
Veja esse link e entenda o que é um repositório:
http://blog.fragmental.com.br/2007/03/01/voce-pergunta-001-daos-e-repositorios/

Espero que tenha entendido, portanto segue abaixo nossa definição do mecanismo de persistência, que é praticamente idêntico ao artigo que eu postei anteriormente, com algumas pequenas alterações, porem espero que o conceito tenha sido entendido.


package org.iprogramming.model.repository;

public interface GenericRepository<T> {
public void persist(T entity);
public void remove(T entity);
}

Acima vemos um repositório genérico, que será usado no nosso repositório de Login abaixo

package org.iprogramming.model.repository;

import java.util.List;
import org.iprogramming.model.entity.Login;

public interface LoginRepository extends GenericRepository<Login>{
public Login findById(Long id);
public Login findByName(String login);
public List<Login> list();
public List<Login> listOrderedBy(String column);
}


Pronto, nosso repositório ja esta definido, é apenas um conceito, onde nos definimos o que nosso repositório de Login deve fazer.
Quando estivermos fazendo nossos EJB, esses serão os nossos contratos para usar as classes abaixo


package org.iprogramming.model.persistence;

import javax.persistence.EntityManager;
import org.iprogramming.model.repository.GenericRepository;

public class GenericDAO<T, DAOImpl extends GenericRepository> implements GenericRepository<T>{

protected EntityManager em;

public DAOImpl setEntityManager(EntityManager em){
this.em = em;
return (DAOImpl)this;
}

public void persist(T entity){
em.persist(entity);
}

public void remove(T entity){
em.remove(entity);
}
}

Sim, novamente o nosso DAO genérico, porêm com menos funções, o exemplo é meramente demonstrativo, vc pode adicionar outras funções genéricas como bem entender.

package org.iprogramming.model.persistence;

import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import org.iprogramming.model.entity.Login;
import org.iprogramming.model.repository.LoginRepository;

public class LoginDAO extends GenericDAO<Login, LoginRepository> implements LoginRepository {

private static final Logger logger = Logger.getLogger(LoginDAO.class.getName());

private static final String msg = "Signals that a method has been invoked at an " +
"illegal or inappropriate time. In other words, the Java environment " +
"or Java application is not in an appropriate state for the requested operation";

public Login findById(Long id) {
return (Login)em.createNamedQuery("Login.findByPkLogin").
setParameter("pkLogin", id).getSingleResult();
}

public Login findByName(String name) {
try{
return (Login)em.createNamedQuery("Login.findByName").
setParameter("name", name).getSingleResult();
}catch(NoResultException e){
logger.log(Level.SEVERE, "Cannot find data");
throw new DAOException(e);
}catch(NonUniqueResultException e){
logger.log(Level.SEVERE, "Have more than one result");
throw new DAOException(e);
}catch(IllegalStateException e){
logger.log(Level.SEVERE, msg, e);
throw new DAOException(e);
}
}

public List<Login> list(){
try{
return em.createQuery("SELECT l FROM Login l").getResultList();
}catch(IllegalStateException e){
logger.log(Level.SEVERE, msg, e);
throw new DAOException(e);
}
}

public List<Login> listOrderedBy(String column) {
return em.createQuery("SELECT login FROM Login login order by login."+column).getResultList();
}
}

Ok, terminamos o nosso mecanismo de persistencia para a entidade login. Segue abaixo a entidade login porem agora estamos usando anotações.

package org.iprogramming.model.entity;

import java.io.Serializable;
import java.util.Collection;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;


@Entity
@Table(name = "login")
@NamedQueries({
@NamedQuery(name = "Login.findByPkLogin",query = "SELECT l FROM Login l WHERE l.pkLogin = :pkLogin"),
@NamedQuery(name = "Login.findByName",query = "SELECT l FROM Login l WHERE l.name = :name"),
@NamedQuery(name = "Login.findByPassword",query = "SELECT l FROM Login l WHERE l.password = :password"),
@NamedQuery(name = "Login.findByEmail",query = "SELECT l FROM Login l WHERE l.email = :email"),
@NamedQuery(name = "Login.findByTelephone",query = "SELECT l FROM Login l WHERE l.telephone = :telephone"),
@NamedQuery(name = "Login.findByDescription",query = "SELECT l FROM Login l WHERE l.description = :description")})

public class Login implements Serializable {
private static final long serialVersionUID = 1L;

@Id
@Column(name = "pk_login", nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="seq_login")
@SequenceGenerator(name="seq_login", sequenceName="seq_login", allocationSize=1)
private Integer pkLogin;

@Column(name = "name", nullable = false)
private String name;
@Column(name = "password", nullable = false)
private String password;
@Column(name = "email", nullable = false)
private String email;
@Column(name = "telephone", nullable = false)
private String telephone;
@Column(name = "description")
private String description;
@JoinTable(name = "login_x_role", joinColumns = {@JoinColumn(name = "fk_login", referencedColumnName = "pk_login")}, inverseJoinColumns = {@JoinColumn(name = "fk_role", referencedColumnName = "pk_role")})
@ManyToMany
private Collection fkRoleCollection;
@JoinTable(name = "login_x_page", joinColumns = {@JoinColumn(name = "fk_login", referencedColumnName = "pk_login")}, inverseJoinColumns = {@JoinColumn(name = "fk_page", referencedColumnName = "pk_page")})
@ManyToMany
private Collection fkPageCollection;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "fkLogin")
private Collection timesheetCollection;

public Login() {
}

public Login(Integer pkLogin) {
this.pkLogin = pkLogin;
}

public Login(Integer pkLogin, String name, String password, String email, String telephone) {
this.pkLogin = pkLogin;
this.name = name;
this.password = password;
this.email = email;
this.telephone = telephone;
}

public Integer getPkLogin() {
return pkLogin;
}

public void setPkLogin(Integer pkLogin) {
this.pkLogin = pkLogin;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getTelephone() {
return telephone;
}

public void setTelephone(String telephone) {
this.telephone = telephone;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public Collection getFkRoleCollection() {
return fkRoleCollection;
}

public void setFkRoleCollection(Collection fkRoleCollection) {
this.fkRoleCollection = fkRoleCollection;
}

public Collection getFkPageCollection() {
return fkPageCollection;
}

public void setFkPageCollection(Collection fkPageCollection) {
this.fkPageCollection = fkPageCollection;
}

public Collection getTimesheetCollection() {
return timesheetCollection;
}

public void setTimesheetCollection(Collection timesheetCollection) {
this.timesheetCollection = timesheetCollection;
}

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

@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Login)) {
return false;
}
Login other = (Login) object;
if ((this.pkLogin == null && other.pkLogin != null) || (this.pkLogin != null && !this.pkLogin.equals(other.pkLogin))) {
return false;
}
return true;
}

@Override
public String toString() {
return "org.iprogramming.model.entity.Login[pkLogin=" + pkLogin + "]";
}

}

Ok, chegamos ao fim desse artigo, no proximo vou usar esse mecanismo através de um EJB e vou descrever como devemos injetar o EntityManager em nossos mecanismos de persistencia.