Guia Hibernate

Hibernate Flush: Write-behind

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (5)  (0)

Veja neste artigo como utilizar o Flush do Hibernate em conjunto com sua técnica write-behind.

Neste artigo veremos um recurso muito importante do Hibernate: o Flush. Você entenderá com detalhes como este recurso funciona e como utilizá-lo da maneira correta.

O Hibernate possui uma técnica chamada “write-behind” que faz com que todas as operações com o banco sejam realizadas o mais tarde possível, para tentar aumentar a performance da aplicação. Em outra palavras, quando você realiza uma operação de DELETE, por exemplo, não necessariamente ela será executada naquele determinado instante, mas o Hibernate irá considerar alguns aspectos para definir quando sua operação deverá ser executada, Estes aspectos veremos mais adiante.

O Flush nada mais é do que a forma com que o Hibernate propaga as alterações da sua aplicação com o banco. Como falamos anteriormente, a técnica “write-behind” faz com que as operações no banco não sejam realizadas pontualmente, fazendo assim com que os dados da aplicação possam ficar diferentes dos dados presentes no banco de dados, Sendo assim, o “flush” irá sincronizar os dados da aplicação com o banco de dados. Na verdade, o que o Hibernate faz é executar todas as operações necessárias (INSERTS,DELETES, UPDATES …).

Mas em que momento o Flush é executado? Existem alguns momentos específicos em que o Hibernate realiza um Flush automático, a fim de sincronizar estes dados com o banco, são eles:

  1. Quando a transação sofre um 'commit';
  2. Antes de um query ser executada;
  3. Quando chamamos explicitamente o método “flush()”.

Então baseado nas três situações acima você saberá quando e porque um flush() está sendo executado, podendo evitá-lo ou simplesmente monitorá-lo. Vamos ver nas seções posteriores exemplos práticos do flush() funcionando.

Exemplos práticos de flush() no Hibernate

Vamos primeiramente mapear nossa classe Client que servirá para todos os nossos exemplos, conforme a Listagem 1.

Listagem 1. Classe Client

  @Entity
  public class Client  {
     
     @Id @GeneratedValue(strategy=GenerationType.AUTO)
     private Long id;
   
     @Column(unique=true,nullable=false)
     private String personalNumber;
     
     @Column
     private String name;
   
  public Long getId() {
         return id;
  }
   
  public void setId(Long id) {
         this.id = id;
  }
   
  public String getPersonalNumber() {
         return personalNumber;
  }
   
  public void setPersonalNumber(String personalNumber) {
         this.personalNumber = personalNumber;
  }
   
  public String getName() {
         return name;
  }
   
  public void setName(String name) {
         this.name = name;
  }
     
     
   }

Nossa classe Client tem um ID que é gerado automaticamente e temos também um “personalNumber” que deve ser sempre único, pois identifica um cliente na nossa aplicação, algo como um token para cada cliente, e o nome do cliente. Vamos agora criar um DAO para realizar as operações com o banco de dados, conforme a Listagem 2.

Listagem 2. DAO do Client

  import java.util.Collection;
   
  import javax.persistence.EntityManager;
  import javax.persistence.PersistenceContext;
   
   
  import org.springframework.dao.IncorrectResultSizeDataAccessException;
   
  public class ClientDao{
         @PersistenceContext
         private EntityManager entityManager;
   
         //Insere um cliente no banco
         public Client createClient(Client client) {
               entityManager.persist(client);
               return client;
         }
   
         /*
          * Busca por um cliente através do personalNumber, caso o 
          * personalNumber não seja encontrado
          * então retorna NULL.
          * */
         @SuppressWarnings("unchecked")
         public Client loadClientWithPersonalNumberOrReturnNull
          (String personalNumber) {
               Collection<Client> clients = (Collection<Client>)  
                 entityManager.createQuery("select c from Client c 
                 where c.personalNumber = :personalNumber")
                 .setParameter("personalNumber", 
                  personalNumber).getResultList();
               if (clients.size()==0)
               {
                      return null;
               }
               if (clients.size()>1)
               {
                      throw new IncorrectResultSizeDataAccessException
                        ("There are "+clients.size()+" client with personal 
                         number "+personalNumber,1,clients.size());
               }
               Client client = clients.iterator().next();
               client.getAddresses().size();
               client.getAccounts().size();
               return client;
         }
         
   
         //Remove um cliente do banco através do seu ID
         public void deleteClient(long clientId) {
            entityManager.remove(entityManager.getReference
             (Client.class, clientId));
         }
   
   
  }

Só entenderemos toda a estrutura acima quando virmos uma sequência de comandos que serão executados a seguir para então explicarmos como o flush() atua nessa sequência. Observe a Listagem 3.

Listagem 3. Sequência de Comandos

         
       Client client = new Client(PERSONAL_NUM);
       LOG.debug("Creating first client");
       clientDao.createClient(client);
       
       LOG.debug("Changing first client");
       client.setName("Carl von Bahnhof");
       
       LOG.debug("Deleting first client");
       clientDao.deleteClient(client.getId());
       
       LOG.debug("Trying to load first client");
       assertNull(clientDao
        .loadClientWithPersonalNumberOrReturnNull
         (PERSONAL_NUM));
       
       LOG.debug("Creating second client");
       clientDao.createClient(new Client(PERSONAL_NUM));

O resultado da execução acima, juntamente com o log do hibernate, será:

Creating first client
insert into Client (name, personalNumber, id) values (?, ?, ?)
Changing first client
Deleting first client
Trying to load first client
delete from Client where id=?
select client0_.id as id2_, client0_.name as name2_,
client0_.personalNumber as personal3_2_
from Client client0_ where client0_.personalNumber=?
Creating second client
insert into Client (name, personalNumber, id) values (?, ?, ?)

Perceba na saída acima que o insert é executado pontualmente, isso porque toda entidade não transiente deve ter um ID. Sendo assim, o hibernate é obrigado a executar o insert para capturar o ID do mesmo. Por outro lado, o delete só é executado depois que tentamos realizar uma busca do cliente, então entraremos naquela regra citada acima: “O flush() é executado antes de uma query ser executada”.

Por outro lado, podemos mudar o comportamento do Hibernate mudando o ID para Sequence, ou seja, o insert não precisará ser feito pontualmente, pois o Hibernate pode consultar apenas a próxima sequence no banco. Então alteramos a propriedade o ID da classe Client para o código da Listagem 4.

Listagem 4. Alterando a propriedade ID da classe Client

  @Id @GeneratedValue(strategy=GenerationType.SEQUENCE)
                private Long id;
  

Agora, após executamos todos os comandos listados acima, na listagem 3, teremos uma saída diferente, veja:

Creating first client
select next value for hibernate_sequence from dual_hibernate_sequence
Changing first client
Deleting first client
Trying to load first client
insert into Client (name, personalNumber, id) values (?, ?, ?)
delete from Client where id=?
select client0_.id as id2_, client0_.name as name2_,
client0_.personalNumber as personal3_2_

from Client client0_ where client0_.personalNumber=?
Creating second client
insert into Client (name, personalNumber, id) values (?, ?, ?)

O que mudou? O hibernate deixou o insert para depois, mais especificamente para antes de ser executado o select. O insert foi executado antes do delete por uma prioridade de execuções que o hibernate adota, são elas:

  1. Inserts, na ordem em que foram executadas;
  2. Updates;
  3. Deleção de elementos de collections;
  4. Inserção de elementos de collections;
  5. Delete, na ordem em que foram executadas.

Ou seja, o Hibernate pode trocar a ordem de execução das suas querys, conforme o mostrado acima, o que em alguns casos pode ocasionar problemas, necessitando assim do flush(). Por conta da ordem mostrada acima é que o insert foi executado logo antes do delete, que foi executado logo antes de query SELECT que disparou o flush() do hibernate.

Vamos supor agora que resolvemos remover a consulta: nesse caso você já deve ter notado que o Hibernate não mais irá realizar o flush(), dado as condições que consideramos logo no início do artigo. Comentamos a seguinte linha:

  //LOG.debug("Trying to load first client");
  //assertNull(clientDao.
    //loadClientWithPersonalNumberOrReturnNull
    //(PERSONAL_NUM));

Após executar o mesmo bloco de código você terá uma saída com um erro, como mostrado abaixo:

Creating first client
select next value for hibernate_sequence from dual_hibernate_sequence
Changing first client
Deleting first client
Creating second client
insert into Client (name, personalNumber, id) values (?, ?, ?)
insert into Client (name, personalNumber, id) values (?, ?, ?)
SQL Error: 0, SQLState: null
failed batch
Could not synchronize database state with session
org.hibernate.exception.GenericJDBCException:
Could not execute JDBC batch update

Não iremos mostrar todo erro, pois é desnecessário, o importante é você visualizar que o Hibernate está tentando executar os dois inserts antes do delete (de acordo com a ordem que mostramos). Seguindo essa lógica ele está inserindo um segundo Client com o mesmo personalNumber do primeiro, porém, o personalNumber é uma chave única, causando o erro acima. Caso você remova a constraint unique do personalNumber, verá que o código funciona normalmente. Mas então qual é a solução se quisermos continuar com a constraint? Executar um flush() logo após o delete, assim forçamos a remoção do Client do banco de dados pontualmente.

O write-behind é uma técnica muito importante para otimização de tarefas no hibernate, mas que pode ocasionar sérios problemas quando não usada de maneira correta. O principal objetivo deste artigo foi demonstrar como utilizar o flush() em conjunto com a técnica write-behind, dando exemplos e conceitos importantes para tal.

 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?