mai 03

Equals com Objeto do tipo proxy do hibernate

Há tempos que não posto nada, estou devendo muita coisa. Mas essa aqui eu tava me ferrando e acho que vale a pena registrar.

A situação é a seguinte: Tenho uma entidade A que se relaciona com a entidade B  em um relacionamento OneToOne, porém não quero utilizar o carregamento eager padrão para este relacionamento. Para resolver isso base adicionar a propriedade fetch=FetchType.LAZY na anotação OneToOne.  Mas como dizia Joseph Climber, a vida é uma caixinha de surpresas. Não é que pra minha surpresa o cache do hibernate resolveu funcionar em um select e manteve um objeto do tipo B que eu já tinha carregado a partir de um do tipo  A. Ou seja, no meio da listagens de objetos do tipo retornados do meu select * , tinha um intruso do tipo lazy do hibernate no meio da listagem WTF???!! . Ou seja, meu equals (gerado pelo eclipse) estava executando a instrução if (getClass() != obj.getClass()) e retornando true, uma vez que aquele bendito objeto lazy intruso não era da mesma classe!!

Agora chega de blablabla e vamos pras soluções:

Solução 1:

Devemos adicionar a seguinte verificação:

Onde tínhamos:  if (getClass() != obj.getClass())

Devemos ter agora:

Class<?> objProxyClass = 
HibernateProxyHelper.getClassWithoutInitializingProxy(obj);
 if (getClass() != obj.getClass() && getClass()!= objProxyClass )

e voi là , na chamada ao método getClassWithoutInitializingproxy é retornada a classe original do meu proxy e o if seguinte funciona sem problema algum!

Solução 2:

Essa solução foi retirada da documentação do hibernate

 if( !(obj instanceof B)) return false;

Essa é bem mais simples e também funciona!!

Agora fica a seu critério a utilização de uma ou de outra.

Lembrando que toda vez que seu objeto tiver a possibilidade de ser um proxy do hibernate nunca utilize diretamente os atributos no equals, faça as comparações sempre utilizando os métodos GETTERS!!!

É isso, se alguém souber uma situação que uma solução ou outra não atenda pode comentar aí, até a próxima.

ago 01

Estados de um objeto gerenciado por um EntityManager

Neste post falarei um pouco de como a especificação JPA 2 trata as entidades manipuladas pelos EntityManagers. As principais funções dos EntityManagers são:

  1. Como o próprio nome já diz, gerenciar as entidades;
  2. Sincronizar o estado dos objetos com os dados correspondentes no banco de dados

Ao falarmos em gerenciar uma entidade nos referimos a como um EntityManager manipula uma entidade, e para isso é necessário entender os estados possíveis que uma instância pode assumir para um EntityManager. São 4 estados possíveis:

  • Transient

Neste estado seu objeto acabou de ser criado e o atributo que foi anotado com @Id não possui valor . Toda vez que você dá um new em um objeto de uma Entidade este é o estado que ele se encontrará.

  • Managed

Neste estado o objeto possui valor no atributo anotado com @Id, sendo este valor atribuido pelo EntityManager e não setado manualmente. Toda vez que ocorrer uma sincronização através de um flush ou um commit, os dados do objeto são atualizados no os dados do banco de dados.

  • Removed

Neste estado,assim como no estado managed, o objeto possui valor no atributo anotado com @Id, sendo este valor atribuido pelo EntityManager e não setado manualmente. Porém quando ocorrer uma sincronização através de um flush ou um commit, o objeto passa a não ter mais vínculo com o banco de dados.

  • Detached

Neste estado,assim como no estado managed, o objeto possui valor no atributo anotado com @Id, sendo este valor atribuido pelo EntityManager ou setado manualmente. Porém este objeto não possui mais vínculo com o EntityManager, logo ele não será mais sincronizado com o banco de dados, e qualquer alteração realizada com ele residirá somente na memória da aplicação e não no banco de dados.

Para trocar de estados o EntityManager nos fornece os seguinte métodos:

  • persist : Transient -> Managed
  • merge : Transient -> Managed / Detached -> Managed
  • evict, clear ou close : Managed -> Detached
  • remove : Managed -> Removed

 

Tendo esses estados em mente e também como transitar entre eles, você consegue ter um controle muito melhor sobre o que você está fazendo ao utilizar JPA.

Fica a dica!

 

 

nov 08

Configurando JPA com Hibernate

Neste post vou mostrar o básico para fazer funcionar o JPA  com hibernate , utilizando o hsqldb, que é um banco de dados leve e muito bom para testes de desenvolvimento.

Inicialmente precisaremos das bibliotecas do hibernate que nesta versão já vem com as do JPA 2, porém isso não influenciará muito nessa configuração básica caso esteja usando o JPA. Agora crie uma pasta lib para colocar os .jar e adicione-os ao build path do projeto.

O segundo passo é criar a nossa Persistence Unit, que provê a definição do contexto de persistência, contendo os metadados relativos a tal.


Para isso deve ser criado um arquivo XML chamado persistence.xml e deve ser colocado dentro da pasta META-INF dentro da sua pasta de códigos fontes, no eclipse, utilizando a configuração default ficaria :  “src/META-INF/persistence.xml” . Este arquivo ficará com a seguinte configuração :

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
    <persistence-unit name="pu" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
            <property name="hibernate.connection.url" value="jdbc:hsqldb:hsql://localhost/nomedobanco" />
            <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver" />

            <property name="hibernate.connection.password" value="senha" />
            <property name="hibernate.connection.username" value="usuario" />

            <property name="hibernate.archive.autodetection" value="class"/>
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="show_sql" value="true" />
            <property name="format_sql" value="true" />

        </properties>
    </persistence-unit>
</persistence>

Obs.: Caso esteja utilizando o JPA  troque a linha da tag <persistence> por:

<persistence version=”1.0″
xmlns=”http://java.sun.com/xml/ns/persistence” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd”>

Feito isso precisamos agora configurar como pegar o EntityManager, que é uma interface para interagir com o contexto de persistência.

Para isso criaremos um singleton que através da fábrica EntityManagerFactory pegaremos uma nova instância de EntityManager caso não tenha nenhuma aberta, ou retornará a que já está aberta. Será criada a classe JPAUtil:

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class JPAUtil {
      private static EntityManagerFactory emf = null;
      private static EntityManager em = null;

      public static EntityManagerFactory getEntityManagerFactory() {
            if (emf == null)
                 emf = Persistence.createEntityManagerFactory("pu");
            return emf;
      }

       public static EntityManager getEntityManager() {
             if (em != null && em.isOpen())
                   return em;
             else {
                   em = getEntityManagerFactory().createEntityManager();
                   return em;
             }
       }

}

Isso já basta para testar se está funcionando, a conexão com o banco. Para testar agora criaremos uma classe de testes utilizando o JUnit para verificar se está tudo ok. Criaremos a classe JPATest:

import static org.junit.Assert.*;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

import org.junit.Test;

public class JPAtest {
	@Test
	public void testaConexaoJPA() {
		EntityManager em = JPAUtil.getEntityManager();
		EntityTransaction transaction = em.getTransaction();
		transaction.begin();
		transaction.commit();
		boolean isconectado = em.isOpen();
		em.close();
		assertTrue(isconectado);
	}
}

Pronto, se tudo estiver correto ficará verde o teste do JUnit. Em outro post mostrarei como implementar um DAO para JPA.

nov 03

Utilizando C3p0 para resolver problemas de falha na conexão com banco

Coloquei uma aplicação no servidor da DailyRazor,  utilizando hibernate com hbm e mysql. Rodando a aplicação localmente no meu tomcat, e apontando para o banco remoto funcionava perfeitamente, porém quando rodava do servidor acessando o banco de dados local, funcionava por um minuto e em seguida a seguinte exceção era lançada: “com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure”. Procurando na internet ( lê-se google) achei vários motivos, que em nada me ajudaram, como por exemplo não permissão de acesso ao banco via TCP/IP, banco fora do ar, etc. Então preferi passar a bola para o pessoal da hospedagem e abri um ticket (Um parenteses aqui: O serviço de suporte do Daily Razor é excelente, resolvem tudo muito rápido.) e logo em seguida foi-me passada a solução: utilizar o C3p0 setando as seguintes configurações no hibernate.cfg.xml:

<property name="hibernate.c3p0.acquire_increment">1</property>
<property name="hibernate.c3p0.idle_test_period">300</property>
<property name="hibernate.c3p0.timeout">120</property>
<property name="hibernate.c3p0.max_size">25</property>
<property name="hibernate.c3p0.min_size">1</property>
<property name="hibernate.c3p0.max_statement">0</property>
<property name="hibernate.c3p0.preferredTestQuery">select 1;</property>


Essa é a configuração default, a única coisa que precisei modificar foi o tempo de timeout, já que eles tem como tempo de timeout para o MySQL 300, segundo eles por questões de estabilidade.
Fica aí a dica, mesmo que não precise recomendo a utilização do C3p0 para pooling de conexão com o banco de dados, pois melhora a performance da aplicação. Nesse blog aqui tem algumas informações a mais.

out 11

Tratar exceção de unique constraint com JPA 2 e Hibernate

Em uma aplicação simples de cadastro, quando se tem campos únicos, como por exemplo cadastro de usuários, onde o nome do usuário não pode se repetir é interessante mostrar para o usuário qual o erro resultante do não cadastramento dos seus dados. No código abaixo eu tenho uma transação que persistirá um objeto do tipo usuário.

em.getTransaction().begin();
em.persist(usuario);
em.getTransaction().commit();

Porém a exceção lançada pelo JPA 2 é apenas PersistenceException, o que não diz muita coisa. Porém ela contém a cadeia de exceções lançadas desde o provider do banco de dados. Logo, uma alternativa para se descobrir qual a causa raiz da exceção é iterar pela causa da exceção, até chegar a BatchUpdateException, que é a exceção que contém uma SQLException como nextException. Esta SQLException é necessária para se extrair o nome da restrição que gerou a exceção.

catch (PersistenceException e) {

			 Throwable lastCause = e;
			 String constraintName =null;
			 while (lastCause != null){
		    	     if(lastCause.toString().startsWith("java.sql.BatchUpdateException")){
		    		     BatchUpdateException bu = (BatchUpdateException) lastCause;
		    		     constraintName = PersistenceUtil.getViolatedConstraintNameExtracter().
                                                      extractConstraintName(bu.getNextException());

		    	     }
		    	  lastCause = lastCause.getCause();

			  }

			   if(constraintName !=null){
		               throw new ConstraintViolationException("Mensagem",
                                     new SQLException(), constraintName);

		          }
        }

Para se extrair o nome da restrição é necessário utilizar o seguinte código.

public static ViolatedConstraintNameExtracter getViolatedConstraintNameExtracter() {

        return EXTRACTER;
   }

	private static ViolatedConstraintNameExtracter EXTRACTER =
           new TemplatedViolatedConstraintNameExtracter() {

	      /**
	       * Extract the name of the violated constraint from the given SQLException.
	       *
	       * @param sqle The exception that was the result of the constraint violation.
	       * @return The extracted constraint name.
	       */
	      public String extractConstraintName(SQLException sqle) {
	         String constraintName = null;

	         int sqlError = Integer.valueOf(JDBCExceptionHelper.extractSqlState(sqle));

	         if(sqlError == 23505){

	            constraintName = extractUsingTemplate("violates unique constraint \"","\"",
                                      sqle.getMessage());
	         }	         

	         return constraintName;
	      }

	   };

Onde ViolatedConstraintNameExtracter é uma interface do pacote org.hibernate.exception e TemplatedViolatedConstraintNameExtracter é a classe abstrata que implementa esta interface.
É necessário implementar o método extractConstraintName(SQLException sqle) herdado da interface.
Nele eu verifico qual o código de erro, utilizando o JDBCExceptionHelper do pacote org.hibernate.exception. No meu caso eu estou utilizando PostgreSQL, e neste endereço: http://www.postgresql.org/docs/8.1/interactive/errcodes-appendix.html temos todos os códigos de erro gerados.
Dependendo do código de erro eu utilizo o método extractUsingTemplate da classe TemplatedViolatedConstraintNameExtracter, passando como parâmetros o padrão da mensagem até o início do nome da restrição, o padrão da mensagem após o nome da restrição e a mensagem da SQLException.

Está um pouco confuso, mas realmente é bem chato tratar essa exceção usando JPA 2, mas basicamente utilizando esses códigos e fazendo as devidas personalizações para a sua aplicação, você conseguirá resolver esse problema.

out 07

Bibliotecas no Eclipse

Um detalhe que vou atentar aqui foi um problema que estava passando, e procurando no google a única solução que o pessoal mostrava era  para rever meu classpath.

O problema: CLASS NOT FOUND EXCEPTION de uma classe contida dentro do jar do hibernate. Porém todos os jar estavam direitinho dentro do classpath e também dentro de WEB-INF/lib. O único porém: os arquivos jar estavam dentro de uma subpasta : WEB-INF/lib/hibernate. Isso não dá erro de compilação, gera apenas warning, avisando de possível lançamento da exceção em tempo de execução.

Fica aí a dica: sempre coloque seus jar diretamente abaixo da WEB-INF/lib, nada de querer organizar direitinho por subpastas, pois pode lançar esta exceção, nem sempre ocorre, mas é melhor não correr esse risco!