sexta-feira, 14 de novembro de 2008

Mapeamento de objetos com JPA e Hibernate

Para auxiliar quem está começando com algum projeto resolvi explicar algumas poucas anotações para realizar o mapeamento de entidades utilizando JPA e Hibernate. Para se realizar a modelagem de classes persistentes e da base de dados pode se utilizar as anotações do JPA e do Hibernate.

Uma das anotações mais utilizadas é a anotação @Entity que define que aquela classe anotada deve ser uma entidade persistida na base de dados. Uma vez utilizada esta anotação, todos os métodos gets e sets e campos (dependendo do seu tipo de acesso) são descobertos e são gerados mapeamento para estas propriedades no banco de dados. Cada uma destas propriedades então gera uma coluna na tabela com o nome da entidade. Para que uma propriedade não seja persistida deve se utilizar a anotação @Transient.

Para se mapear objetos complexos devem ser usadas as anotações @OneToOne, @OneToMany, @ManyToOne e @ManyToMany. Quando mapeamos objetos com o tipo @ManyToMany o próprio JPA é responsável por gerar a tabela entre os dois objetos.

Quando se mapeia objetos deve se definir a política de quando estes objetos devem ser buscados. Normalmente quando se busca um objeto os outros objetos nem sempre precisam ser exibidos e buscá-los no banco causaria uma perda de desempenho indesejável. Um exemplo seria uma busca de pessoas que somente queremos mostrar seus dados e não seu endereço. Para este problema podemos mapear a propriedade como LAZY que indica que o objeto mapeado não é buscado inicialmente e sim somente depois dela ser necessária. Para isto definimos na anotação de mapeamento o trecho fetch = FetchType.LAZY. Para colocar propriedades simples LAZY deve se utilizar a seguinte anotação @Basic(fetch=FetchType.LAZY).

Para constantes únicas como, por exemplo, CPF de uma pessoa é interessante se utilizar a seguinte anotação @Table (uniqueConstraints = @UniqueConstraint ( columnNames = { "cpf" })). Para determinar que uma propriedade não deva ser nula deve-se utilizar a anotação @NotNull. Outra anotação importante é a anotação @NotEmpty que verifica se alguma propriedade do tipo String não é vazia. Para se determinar o tamanho máximo e mínimo de um campo do tipo String pode se utilizar a anotação @Length(min=3, Max=100) e para se determinar qual o intervalo válido de valores de campos inteiros pode se utilizar a anotação @Range(min=12, max=12).

Para propriedades que podem ser validadas com expressões regulares deve se utilizar a seguinte anotação @Pattern(regex="expressão regular") e para validação de email pode se utilizar a anotação @Email. Para outras validações mais complexas podem-se utilizar as anotações @AssertTrue, @AssertFalse, @Valid.

As validações devem ser utilizadas sempre que possível, para que o modelo esteja sempre coerente e correto.

É isto aí pessoal. Existem várias outras anotações de mapeamento que não foram citadas, mas em um outro momento iremos falar delas. Quaisquer problemas enviem mensagem.


quarta-feira, 12 de novembro de 2008

Gerenciamento de Transações com JTA

Segue abaixo algumas considerações acerca da API JTA e de como EJB3 suporta transações declarativamente.

Normalmente as transações em aplicações são controladas em nível de métodos de negócio. Para se declarar que se está sendo utilizada alguma transação deve se utilizar a anotação @TransactionAttribute.

Por padrão a transação utiliza a estratégia REQUIRED, que significa que se o método do serviço não possuir alguma transação, uma será criada, e se já possuir uma transação esta será aproveitada. Esta é a estratégia que deve ser utilizada na maior parte dos casos uma vez que é bem lógica.

O que esta estratégia quer dizer resumidamente? Quando você estiver em um contexto de transação e caso ocorra alguma exceção do tipo Runtime ou exceção com a anotação @ApplicationException(rollback=true), deve ser dado o rollback na base de dados. Com esta estratégia padrão sempre será dado o rollback da transação mais externa, o que na maioria dos casos faz todo sentido.

Outras estratégias são:
  • SUPPORTS - Não cria nenhuma transação, mas se já existir alguma ela será utilizada.
  • MANDATORY - Requer que quem chamou o método tenha criado uma transação.
  • NEVER - Proíbe que quem chamou o método tenha iniciado uma transação.
  • REQUIRESNEW - Sempre começa uma nova transação, suspendendo a antiga existente.
  • NOTSUPPORTED - Suspende qualquer transação ativa.
Como padrão, pode-se anotar toda a classe com a anotação @TransactionAttribute, o que determina que todos os métodos da classe utilizam transação e com a estratégia REQUIRED. Caso exista algum método que deva usar outra estratégia basta sobrescrever a estratégia usando acima do método alguma das anotações abaixo:
  • @TransactionAttribute(TransactionAttributeType.NEVER)
  • @TransactionAttribute(TransactionAttributeType.MANDATORY)
  • @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
  • @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  • @TransactionAttribute(TransactionAttributeType.SUPPORTS)
Agora é definir para os métodos qual o melhor tipo de estratégia de transação e sobreescrever.É isto aí pessoal. Qualquer problema me avisem.

sexta-feira, 7 de novembro de 2008

DAO Factory Genérico EJB3

Pessoal,

Como dito pelo excelente post do meu amigo Ricardo Ferreira em http://architecture-journal.blogspot.com/2007/07/enterprise-java-beans-30-anti-patterns.html não é uma boa prática termos todos os DAOs controlados pelo EJB apenas para podermos injetar o EntityManager.

Então fiz um DAOFactory bem genérico que serve muito bem aos meus propósitos, mas que pode ajudar quem quer criar uma factory para EJB3. Para se buscar o DAO, deve-se então injetar o DaoFactory e utilizar o método getDAO passando como argumento a classe persistente a ser utilizada.

Segue abaixo a classe:

@Stateless
@SuppressWarnings("unchecked")
public class DAOFactory implements DaoFactoryLocal {

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

private final static String POS_FIXO_DAO = "DAO";
private final static String PACOTE_ENTIDADE = ".entidade.";
private final static String PACOTE_DAO = ".dao.";

private static String getDAOName(Class classeEntidade) {
String resultado = classeEntidade.getName().replace(PACOTE_ENTIDADE,
PACOTE_DAO);
resultado = resultado.concat(POS_FIXO_DAO);


return resultado;
}

@Override
public GenericDAOImpl getDAO(Class classeEntidade) {

GenericDAOImpl result = null;

try {
result = (GenericDAOImpl) Class.forName(getDAOName(classeEntidade))
.newInstance();
result.setEntityManager(em);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}


return result;
}

}



Segue abaixo um exemplo de sua utilização:

@EJB
private DaoFactoryLocal daoFactory;

@SuppressWarnings("unchecked")
protected GenericDAOImpl getDAO() {
return daoFactory.getDAO(classePersistente);

}


Qualquer problema me avisem.

terça-feira, 4 de novembro de 2008

DAO Genérico com JPA e Hibernate


Olá pessoal,

Criei uma estrutura inicial de um DAO Genérico que gostaria de compartilhar com vcs. Ainda não existe todos os métodos que vou precisar, mas vou atualizando aqui a cada mudança no código. Para esta implementação utilizei JPA + Hibernate e para meu caso vem me atendendo bem por enquanto. Segue abaixo a implementação:


-----------------------------------------------


/**
* Esta classe é responsável por conter as operações genéricas para realização
* do acesso aos dados do sistema.
*
* @author Samuel
*
* @param
*/
@SuppressWarnings("unchecked")
public abstract class GenericDAOImpl {

private EntityManager entityManager;

private Class classePersistente;

/**
* Contrutor que guarda o tipo atual da classe T.
*/
public GenericDAOImpl() {
this.classePersistente = (Class) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}

/**
* Atualiza o objeto que se encontra em memória.
*
* @param object
* objeto a ser atualizado
*/
public final void refresh(T object) {
getEntityManager().refresh(object);
}

/**
* Executa o flush no entity manager.
*
*/
public final void flush() {
getEntityManager().flush();
}

/**
* Executa o flush no entity manager.
*
*/
public final void clear() {
flush();
getEntityManager().clear();
}


/**
* Executa o merge do objeto que se encontra em memória.
*
* @param objeto
* a ser realizado o merge
* @return objeto que foi executado o merge
*/
public final T merge(T objeto) {
objeto = getEntityManager().merge(objeto);
return objeto;
}

/**
* Salva o objeto atual na base de dados.
*
* @param objeto
* a ser salvo
*/
public final void salvar(T objeto) {
getEntityManager().persist(objeto);
}

/**
* Remove o objeto da base de dados.
*
* @param objeto
* a ser removido
*/
public final void remover(T objeto) {
getEntityManager().remove(objeto);
}

/**
* Remove o objeto uma vez passado sua chave como parâmetro.
*
* @param chave
* identificadora do objeto
*/
public final void removerPorChave(Integer chave) {
getEntityManager().createQuery(
"delete from " + getClassePersistente().getName()
+ " where id = " + chave).executeUpdate();
}

/**
* Busca o objeto uma vez passado sua chave como parâmetro.
*
* @param chave
* identificador
* @return Objeto do tipo T
*/
public final T buscarPorChave(Integer chave) {
T instance = null;
try {
instance = getEntityManager().find(getClassePersistente(), chave);
} catch (RuntimeException re) {
re.printStackTrace();
}
return instance;
}

/**
* Busca o objeto de acordo com o objeto preenchido com os valores passado
* como exemplo.
*
* @param objeto
* utilizado para realizar a busca
* @param ordenacoes
* lista de critérios de ordenação
* @return Lista de objetos retornada
*/
public final List buscarPorExemplo(T objeto, Order... ordenacoes) {
Session session = (Session) getEntityManager().getDelegate();
Example example = criaExemplo(objeto);
Criteria criteria = session.createCriteria(objeto.getClass()).add(
example);
for (int i = 0; i < ordenacoes.length; i++) {
criteria.addOrder(ordenacoes[i]);
}
return (List) criteria.list();
}

/**
* Busca o objeto de acordo com o objeto preenchido com os valores passado
* como exemplo.
*
* @param objeto
* @param indiceInicial
* @param indiceFinal
* @param ordenacoes
* lista de critérios de ordenação.
* @return Lista de orden
*/
public final List buscarPorExemplo(T objeto, Integer indiceInicial,
Integer indiceFinal, Order... ordenacoes) {
Example example = criaExemplo(objeto);
Criteria criteria = criaCriteria().add(example);
criteria.setFirstResult(indiceInicial);
criteria.setMaxResults(indiceFinal);

for (int i = 0; i < ordenacoes.length; i++) {
criteria.addOrder(ordenacoes[i]);
}

return (List) criteria.list();
}

/**
* Retorna a quantidade total de objetos para aquela entidade específica.
*
* @return quantidade total de objetos
*/
public final int buscaQuantidadeTotal() {
Criteria criteria = criaCriteria();
criteria.setProjection(Projections.rowCount());
return (Integer) criteria.uniqueResult();
}

/**
* Busca todos os objetos para aquela entidade específica.
*
* @param ordenacoes
* lista de ordenações para pesquisa
* @return lista de todos os objetos da entidade
*/
public List buscarTodos(Order... ordenacoes) {
List results = null;
try {
Query query = getEntityManager().createQuery(
"from " + getClassePersistente().getName()
+ adicionaOrderByHql(ordenacoes));
results = query.getResultList();
} catch (RuntimeException re) {
re.printStackTrace();
}
return results;
}

/**
*
* Busca todos os objetos de uma entidade específica de um índice inicial
* até um índice final.
*
* @param indiceInicial
* indice inicial da busca
* @param indiceFinal
* indice final da pesquisa.
* @param ordenacoes
* lista de ordenação a ser criado
* @return uma lista de objetos do tipo T
*/
public List buscarTodos(Integer indiceInicial,
Integer indiceFinal, Order... ordenacoes) {
List results = null;
try {
Query query = getEntityManager().createQuery(
"from " + getClassePersistente().getName()
+ adicionaOrderByHql(ordenacoes));
query.setFirstResult(indiceInicial);
query.setMaxResults(indiceFinal);

results = (List) query.getResultList();
} catch (RuntimeException re) {
re.printStackTrace();
}
return results;
}

/**
* Utilizado para se injetar o Entity manager no DAO.
*
* @param entityManager
* entity manager
*/
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}

/**
* Utilizado para se utilizar o entity manager nos DAOS que herdam do DAO
* genérico.
*
* @return Entity manager.
*/
protected EntityManager getEntityManager() {
return entityManager;
}

/**
* Adiciona o orderBy no final da query a ser utilizada.
*
* @param ordenacoes
* a serem utilizadas para a busca
* @return string com o orderBy
*/
protected final static String adicionaOrderByHql(Order... ordenacoes) {
String result = "";
if (ordenacoes.length > 0) {
StringBuilder builder = new StringBuilder(" order by ");
for (int i = 0; i < ordenacoes.length - 1; i++) {
builder.append(ordenacoes[i].toString());
builder.append(", ");
}
builder.append(ordenacoes[ordenacoes.length - 1]);
result = builder.toString();
}

return result;
}

/**
* Busca a classe persistente do objeto utilizado na classe.
*
* @return classe persistente
*/
protected final Class getClassePersistente() {
return classePersistente;
}

/**
* Retorna o objeto da clases Criteria.
*
* @return um objeto do tipo Criteria do Hibernate
*/
protected final Criteria criaCriteria() {
Session session = (Session) getEntityManager().getDelegate();
return session.createCriteria(getClassePersistente());
}

/**
* Método utilizado para criar o objeto Example. Este objeto é utilizado
* para realizar a busca por exemplo.
*
* @param objeto
* sobre o qual o Example será criado
* @return em objeto do tipo Example
*/
protected final Example criaExemplo(T objeto) {

Example example = Example.create(objeto);
example.enableLike(MatchMode.ANYWHERE);
example.excludeZeroes();
example.ignoreCase();

return example;
}

}


-----------------------------------------------

Para ilustrar como esta classe é utilizada mostrarei abaixo como extender este DAO genérico:

-----------------------------------------------



public class PessoaDAO extends GenericDAOImpl {

public List buscarTodosTelefoneEndereco(Integer indiceInicial, Integer indiceFinal,
Order... ordenacoes) {
List pessoas = super.buscarTodos(indiceInicial, indiceFinal,
ordenacoes);

for (Pessoa pessoa : pessoas) {
// Carrega os relazionamentos que são LAZY. Este método chama o
// método initialize do Hibernate.
pessoa.getTelefones().size();
pessoa.getEnderecos().size();
}

return pessoas;
}

@SuppressWarnings("unchecked")
public List buscarTodosHQL() {
Query query = getEntityManager().createNamedQuery("selectAllHQL");
List pessoas = query.getResultList();

return pessoas;
}

@SuppressWarnings("unchecked")
public List buscarTodosSQL() {

Query query = getEntityManager().createNamedQuery("selectAllNativo");
List pessoas = query.getResultList();

return pessoas;
}

@SuppressWarnings("unchecked")
public List buscarTodosComTelefone() {

List resultado = new ArrayList();
Query query = getEntityManager().createNamedQuery("selectAllNativoTelefone");

List resultados = query.getResultList();
for (Object object : resultados) {
Object[] arranjo = (Object[]) object;
Pessoa pessoa = (Pessoa) arranjo[0];
Telefone telefone = (Telefone) arranjo[1];
Collection telefones = new ArrayList();
telefones.add(telefone);
pessoa.setTelefones(telefones);

resultado.add(pessoa);
}

return resultado;
}



-----------------------------------------------

Em breve postarei como realizo a injeção do Entity Manager utilizando a classe DAOFactory. Qualquer dúvida ou coisa que precisarem me mandem mensagem.

Livro de EJB3 interessante

Estou começando um novo projeto e estou na parte das definições de tecnologias a serem utilizadas. Por falta de tempo para poder esperar algum livro em inglês resolvi comprar um livro em português mesmo sobre EJB3. O livro se encontra abaixo:


Enterprise JavaBeans 3.0 (Editora O'Reilly)

Pra minha surpresa a tradução do livro é muito boa e o livro é excelente. Possui exemplos bem explicados e cobre todos os tópicos de EJB3. As explicações são muito bem escritas e explicam desde o básico ao avançado. O livro em Inglês é muito bem avaliado na amazon e pelo que ví a versão traduzida não deixou muito a desejar.

Vai aí uma dica para quem quer aprender EJB3 e também não tem tempo para comprar um livro em inglês.