set 05

Mapeando chaves compostas com JPA 2

Muitas vezes utilizamos em nossas tabelas chaves compostas para identificação de uma tupla, um exemplo seria na tabela empregado a chave primária ser composta pelos atributos cpf e matrícula do empregado, vocês podem dizer que bastaria o cpf ou a matrícula para identificar o empregado, mas aqui estou apenas exemplificando para mostrar como mapeamos esta chave composta utilizando JPA2.

Para isso a JPA nos oferece 2 estratégias de mapeamento:

  • IdClass
  • EmbeddedId

Eu até o momento ainda não identifiquei uma diferença de desempenho entre as duas estratégias, nem vejo vantagem em questão de verbosidade de uma em relação a outra, portanto mostrarei as 2 e quem quiser escolhe a que melhor convir.

Utilizando IdClass 

Para se utilizar de idClass precisamos criar uma classe com os atributos que compõem nossa chave primária da seguinte maneira:

public class EmpregadoId {

private String cpf;
private String matricula;

//getters e setters
@Override
public int hashCode() {
//código hashcode
}

@Override
public boolean equals(Object obj) {
//código equals
}
}

Devemos sempre implementar os métodos hashcode e equals da nossa classe de Id, o hibernate apenas dará um alerta caso não estejam implementados estes métodos.

Agora na nossa classe de entidade devemos ter o seguinte código:

@Entity
@IdClass(EmpregadoId.class)
public class Empregado {

@Id
private String cpf;

@Id
private String matricula;

//Outros atributos e getters e setters

Ou seja, basta anotar com @IdClass a classe de entidade, passando por parâmetro a classe que contém os atributos do id, e devemos anotar com @Id os atributos que compõem o id, que são os mesmos da classe de id.

E é só, nessa estratégia a classe que representa o id não precisa de anotações, e não será utilizada pela nossa aplicação, porém é necessária para que a estratégia funcione.

 

Utilizando EmbeddedId

Nesta  estratégia também precisamos de uma classe com os atributos da chave primária, porém esta classe deve ser anotada como @Embeddable e deve obrigatoriamente implementar a interface Serializable , além de implementar os métodos hashcode e equals assim como na estratégia anterior. Assim temos a classe de id da seguinte maneira:

@Embeddable
public class EmpregadoId implements Serializable {

private static final long serialVersionUID = 1L;
private String cpf;
private String matricula;

//getters e setters
@Override
public int hashCode() {
//código hashcode
}

@Override
public boolean equals(Object obj) {
//código equals
}
}

Já a nossa entidade ficará da seguinte maneira:

@Entity
public class Empregado {

@EmbeddedId
private EmpregadoId id;

//Outros atributos e getters e setters

Ou seja, agora para acessar os atributos da chave primária composta devemos buscar através do atributo id que será do tipo EmpregadoId, que é anotado com @EmbeddedId  .

_____________________________________________________________________________________

Vimos então como é simples mapear chaves primárias compostas utilizando JPA 2, o único problema é que sempre precisamos de uma classe auxiliar que possuirá os atributos que compõem a chave.

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!

 

 

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.