Guia Hibernate

Fazendo cache de objetos com Hibernate

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)  (1)

Este artigo vai demonstrar como fazer cache de objetos com o Hibernate e como utilizar o Second Level Cache do Hibernate, que permite ter um maior controle sobre os objetos que estão em cache.

Nesse artigo vamos explicar o funcionamento do cache da Session (cache simples), e sobre o Second Level Cache (cache avançado).

Hibernate

Para rodar o exemplo deste artigo, faça o download do Hibernate 3.1.3: http://prdownloads.sourceforge.net/hibernate/hibernate-3.1.3.zip

Após fazer o download, coloque o arquivo “hibernate.jar” no classpath do seu projeto, assim como todos os outros jar’s encontrados na pasta “lib”.

Criando o POJO

Para testar o cache, vamos criar uma simples classe “Notícia”.

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

      "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

      "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

 

<hibernate-mapping>

<class

    name="model.Noticia"

    table="Noticia"

    >

      <id

        name="id"

        type="java.lang.Integer"

        column="ID"

    >

        <generator class="native" />

    </id>

      <property

        name="titulo"

        type="java.lang.String"

        column="titulo"

        length="50"

        not-null="true"

    />

</class>

</hibernate-mapping>

 

public class Noticia {

      private Integer id;

      private String titulo;

//get e set …

}

Criando a Tabela Notícia:

create table Noticia (

ID integer generated by default as identity (start with 1),

titulo varchar(50) not null,

primary key (ID))

E o arquivo hibernate.cfg.xml (Altere a URL e driver JDBC para o banco de dados de sua preferência):

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

 

<hibernate-configuration>

  <session-factory>

 

    <!-- HSQLDB -->

    <property name="hibernate.connection.driver_class">

        org.hsqldb.jdbcDriver</property>

    <property name="hibernate.connection.url">

        jdbc:hsqldb:hsql://localhost/hello</property>

    <property name="hibernate.connection.username">sa</property>

    <property name="hibernate.connection.password" />

    <property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property>

 

    <property name="hibernate.show_sql">true</property>

 

    <mapping resource="model/Noticia.hbm.xml" />

 

  </session-factory>

</hibernate-configuration>

First-Level cache - Session Cache

Sempre que o Hibernate busca um objeto utilizando os métodos “load” e “get”, o resultado é armazenado no cache da Session, conhecido como first-level cache. Este cache é default no Hibernate e não pode ser desabilitado. O objeto “Session” representa uma conexão com o banco de dados, e não deve “viver” por muito tempo, desta forma é uma boa prática (recomendado) que a Session seja eliminada ao final de uma transação/requisição com o servidor. Desta forma, a conexão com o banco é fechada (ou retornada ao pool) e o cache da Session também é apagado.

O Exemplo abaixo faz uma simples busca de uma notícia com o ID=1. Note que a busca é feita 2 vezes para a Notícia, mas o Hibernate vai acessar o banco de dados apenas uma vez. Entre a 1ª e a 2ª busca, foi incluído um trecho de código que faz um UPDATE com JDBC, para atualizar o título da notícia. Porém como era esperado, o Hibernate manteve a Notícia no cache da Session, e o título da 2ª busca ainda é uma informação antiga.

Ao rodar o exemplo, vamos supor que existe uma notícia na tabela com o ID = 1.

public class Teste1 {

      public static void main(String[] args) throws Exception {

            SessionFactory sessionFactory =

                         new Configuration().configure().buildSessionFactory();

            Session session = sessionFactory.openSession();

            Transaction t = session.beginTransaction();

 

            //Recupera a notícia do banco. Insere no cache da Session

            System.out.println("1 - pre session.get");

            Noticia n = (Noticia) session.get(Noticia.class, new Integer(1));

            System.out.println("1 - pos session.get");

            System.out.println(n.getTitulo());

           

            //Atualiza a tabela usando JDBC.

            Class.forName("org.hsqldb.jdbcDriver");

            Connection conn = DriverManager.getConnection

                                ("jdbc:hsqldb:hsql://localhost/hello","sa","");

            Statement stmt = conn.createStatement();

            stmt.executeUpdate("update noticia set titulo = 'novo titulo'");

 

//Tire o comentário para remover a Notícia do cache da Session

            //session.evict(n);

           

//A segunda busca vai imprimir o título antigo da notícia.

            System.out.println("2 - pre session.get");

            n = (Noticia) session.get(Noticia.class, new Integer(1));

            System.out.println("2 - pos session.get");

            System.out.println(n.getTitulo());

 

            t.commit();

            session.close();

      }

}

Saída:

pre session.get: Hibernate: select noticia0_.ID as ID0_0_, noticia0_.titulo as titulo0_0_, noticia0_.texto as texto0_0_ from Noticia noticia0_ where noticia0_.ID=?

pos session.get: Titulo antigo

  • pre session.get
  • pos session.get

Titulo antigo

O texto “Título antigo” apareceu duas vezes, como era esperado.

Note que está comentado a linha com o código: “session.evict(n);”. O método evict(object) da classe Session elimina o objeto do cache, fazendo com que o Hibernate consulte o banco de dados todas as vezes que precisar de determinada informação. Faça o teste!

Entendendo o First-Lavel cache

Sempre que uma nova Session é aberta, uma nova conexão com o servidor é realizada e um novo cache é criado. Quando a Session for fechada, o seu cache correspondente também é eliminado. O cache de uma Session não interfere em outra Session.

Para comprovar isto, altere o código anterior, para fechar a Session atual, e abrir uma nova antes de fazer a segunda consulta.

//1ª consulta…
//JDBC com update… 

t.commit();
session.close();//fecha a 1ª session (fecha conexão e elimina o cache) 

session = sessionFactory.openSession(); //cria uma nova session
t = session.beginTransaction(); 

//nesta session os dados serão consultados do banco de dados
//2ª consulta…

O resultado da execução deste código pode ser visualizado abaixo:

  • pre session.get: Hibernate: select noticia0_.ID as ID0_0_, noticia0_.titulo as titulo0_0_, noticia0_.texto as texto0_0_ from Noticia noticia0_ where noticia0_.ID=?
  • pos session.get: Titulo Antigo
  • pre session.get: Hibernate: select noticia0_.ID as ID0_0_, noticia0_.titulo as titulo0_0_, noticia0_.texto as texto0_0_ from Noticia noticia0_ where noticia0_.ID=?

pos session.get: Novo titulo

Note que o Hibernate fez 2 consultas no banco. E que o título da notícia foi atualizano na segunda busca.

Lembre-se de que outra maneira de eliminar um objeto da cache (sem fechar a Session) é chamando o método “session.evict(obj)”.

Este cache é muito simples para ser utilizado em aplicações Web, onde geralmente a cada requisição ao servidor, uma nova Session é criada. Então fica a pergunta, como utilizar cache de objetos mesmo Sessions (conexões) diferentes?

Para isto existe o Second-Level cache, que é uma estratégia de cache um pouco mais agressiva e eficiente, onde alguns frameworks como o EHCache e OSCache são utilizados. Este cache será explicado mais abaixo:

EHCache

O EHCache é o framework default de cache do Hibernate. Para utilizá-lo basta colocar o ehcache.jar no classpath da aplicação. Para utilizar outro framework, como por exemplo o OSCache, é necessário alterar o arquivo hibernate.cfg.xml e incluir a seguinte linha:

<property name="hibernate.cache.provider_class">net.sf.hibernate.cache.OSCacheProvider </property>

Para este artigo, não é necessário alterar esta propriedade, pois vamos assumir o EHCacheProvider (default).

Ativando o cache para a classe Notícia

Para utilizar o cache, é necessário configurar o hibernate.conf.xml, conforme visualizado abaixo.

<mapping resource="model/Noticia.hbm.xml" />   

<class-cache class="model.Noticia" usage="read-write" />

Neste exemplo vamos utilizar o tipo “read-write” o qual permite a leitura e gravação de novas informações. Outro tipo que pode ser utilizado é o “read-only” para as informações que não nunca serão atualizadas.

Second Level Cache Hibernate

Para configurar as propriedades do cache para a Notícia, é necessário adicionar o arquivo ehcache.xml no classpath da aplicação. Copie este arquivo da pasta de distribuição do Hibernate e insira as seguintes configurações:

<cache name="model.Noticia"

        maxElementsInMemory="100"

        eternal="false"

        timeToIdleSeconds="300"

        timeToLiveSeconds="600"

        overflowToDisk="false"

/>

Note que neste caso o nome do cache é o nome completo da classe “model.Noticia”. Caso o nome do cache não seja encontrado, o cache default será utilizado. Este cache também é configurado no arquivo ehcache.xml com a tag “<defaultCache>”.

Abaixo as explicações de cada propriedade que pode ser configurada:

  • maxElementsInMemory: número máximo de objetos que podem ficar armazenados em memória.
  • eternal: se configurado para “true”, significa que o cache nunca vai expirar.
  • timeToIdleSeconds: tempo em segundos que um objeto pode permanecer inutilizado no cache.
  • timeToLiveSeconds: tempo em segundos que um objeto pode ficar em cache.
  • overflowToDisk: se configurado para “true” e caso o limite de objetos em memória seja superior ao definido pela propriedade “maxElementsInMemory”, as informações serão armazenadas em disco.

Pronto, isto é tudo. Agora os objetos “Notícia” serão armazenados em cache.

Testando o cache

Para testar o cache, vamos fazer o mesmo exemplo que foi utilizado no artigo anterior. Inicialmente é feita uma consulta no banco de dados com a primeira Session. Logo depois a tabela Notícia é atualizada utilizando JDBC. Então a Session atual é fechada, e uma nova é aberta. Através do Second Level Cache, a segunda Session consegue recuperar os objetos em cache, respeitando as configurações de limite de objetos em memória e tempo de expiração definidos no arquivo ehcache.xml.

//1ª consulta…

//JDBC com update… 

//fecha a 1ª session (fecha conexão e elimina o first level cache)

t.commit();

session.close(); 

//cria uma nova session

session = sessionFactory.openSession();

t = session.beginTransaction(); 

//2ª consulta…

//As informações retornadas aqui estão em cache

Faça o teste, e você verá que o resultado retornado pela 2ª consulta é o mesmo retornado pela 1ª, uma vez que o cache é utilizado, e não é feita uma consulta no banco de dados.

Cache para a Query

A classe Query é frequentemente utilizada para realizar consultas no banco de dados utilizando HQL, como por exemplo:

Noticia n = (Noticia) session.createQuery("from Noticia where id = 1").setCacheable(true).uniqueResult();

Note que para utilizar o cache da Query, o método setCacheable(true) foi chamado para informar o Hibernate que é necessário fazer cache da consulta.

Porém para o cache funcionar, é necessário adicionar a seguinte linha no arquivo hibernate.cfg.xml:

<property name="hibernate.cache.use_query_cache">true</property>

Por default, o nome do cache (conhecido como região) utilizado é “org.hibernate.cache.StandardQueryCache”. Portanto, do mesmo modo que anteriormente foi configurado o cache para a classe Notícia, é necessário configurar as propriedades do cache para a Query, conforme visualizado abaixo:

<cache name="org.hibernate.cache.StandardQueryCache"

        …… outras propriedades aqui

/>

Caso seja necessário customizar o cache a ser utilizado por cada consulta, pode-se utilizar o método “setCacheRegion” da classe Query:

Noticia n = (Noticia) session.createQuery("from Noticia where id = 1").setCacheable(true).setCacheRegion("cacheNoticia").uniqueResult();

Desta forma uma configuração de cache precisa ser criada para o nome “cacheNoticia”, conforme demonstrado abaixo:

<cache name="consultaNoticia"

       …… outras propriedades aqui

/>

Log4j

O log4j pode ser utilizado para debugar a execução do cache. Para ativar os logs, insira a seguinte linha no arquivo log4j.properties:

log4j.logger.org.hibernate.cache=debug,stdout

Para demonstrar os logs, execute o código abaixo:

public class TesteCache3 {

      public static void main(String[] args) throws Exception {

            SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();

            Session session = sessionFactory.openSession();

            Transaction t = session.beginTransaction();

 

            //Recupera a notícia do banco.

            System.out.println("1 - pre session.get");

            Noticia n = (Noticia) session.createQuery("from Noticia where id = 1").setCacheable(true).setCacheRegion("consultaNoticia").uniqueResult();

            System.out.println("1 - pos session.get");

            System.out.println(n.getTitulo());

           

//          Fecha a 1ª session

            t.commit();

            session.close();

 

            //    Atualiza a tabela usando JDBC.

            Class.forName("org.hsqldb.jdbcDriver");

            Connection conn = DriverManager.getConnection("jdbc:hsqldb:hsql://localhost/hello","sa","");

            Statement stmt = conn.createStatement();

            stmt.executeUpdate("update noticia set titulo = 'novo titulo'");

           

            session = sessionFactory.openSession();

            t = session.beginTransaction();

 

//Faz a busca na 2ª session e utiliza o cache “consultaNoticia”

            System.out.println("2 - pre session.get");

            n = (Noticia) session.createQuery("from Noticia where id = 1").setCacheable(true).setCacheRegion("consultaNoticia").uniqueResult();

            System.out.println("2 - pos session.get");

            System.out.println(n.getTitulo());

 

            t.commit();

            session.close();

      }

}

A saída do programa pode ser visualizada abaixo:

DEBUG CacheFactory:39 - instantiating cache region: model.Noticia usage strategy: read-write
INFO UpdateTimestampsCache:43 - starting update timestamps cache at region: org.hibernate.cache.UpdateTimestampsCache
WARN EhCacheProvider:103 - Could not find configuration [org.hibernate.cache.UpdateTimestampsCache]; using defaults.
DEBUG EhCacheProvider:106 - started EHCache region: org.hibernate.cache.UpdateTimestampsCache
INFO StandardQueryCache:51 - starting query cache at region: org.hibernate.cache.StandardQueryCache

pre session.get

INFO StandardQueryCache:51 - starting query cache at region: consultaNoticia
WARN EhCacheProvider:103 - Could not find configuration [consultaNoticia]; using defaults.
DEBUG EhCacheProvider:106 - started EHCache region: consultaNoticia
DEBUG StandardQueryCache:93 - checking cached query results in region: consultaNoticia
DEBUG EhCache:104 - key: sql: select noticia0_.ID as ID0_, noticia0_.titulo as titulo0_, noticia0_.texto as texto0_ from Noticia noticia0_ where noticia0_.ID=1; parameters: ; named parameters: {}
DEBUG EhCache:113 - Element for sql: select noticia0_.ID as ID0_, noticia0_.titulo as titulo0_, noticia0_.texto as texto0_ from Noticia noticia0_ where noticia0_.ID=1; parameters: ; named parameters: {} is null
DEBUG StandardQueryCache:98 - query results were not found in cache
Hibernate: select noticia0_.ID as ID0_, noticia0_.titulo as titulo0_, noticia0_.texto as texto0_ from Noticia noticia0_ where noticia0_.ID=1
DEBUG ReadWriteCache:148 - Caching: model.Noticia#1
DEBUG EhCache:104 - key: model.Noticia#1
DEBUG EhCache:113 - Element for model.Noticia#1 is null
DEBUG ReadWriteCache:160 - Cached: model.Noticia#1
DEBUG StandardQueryCache:67 - caching query results in region: consultaNoticia

pos session.get

Titulo antigo

pre session.get

DEBUG StandardQueryCache:93 - checking cached query results in region: consultaNoticia
DEBUG EhCache:104 - key: sql: select noticia0_.ID as ID0_, noticia0_.titulo as titulo0_, noticia0_.texto as texto0_ from Noticia noticia0_ where noticia0_.ID=1; parameters: ; named parameters: {}
DEBUG StandardQueryCache:147 - Checking query spaces for up-to-dateness: [Noticia]
DEBUG EhCache:104 - key: Noticia
DEBUG EhCache:113 - Element for Noticia is null
DEBUG StandardQueryCache:108 - returning cached query results
DEBUG ReadWriteCache:75 - Cache lookup: model.Noticia#1
DEBUG EhCache:104 - key: model.Noticia#1
DEBUG ReadWriteCache:85 - Cache hit: model.Noticia#1

pos session.get

Titulo antigo

Conclusão

Neste artigo foi explicado como configurar o Second Level Cache do Hibernate, e como demonstrado é uma tarefa relativamente simples.

Fazer cache de objetos pode aumentar a performance de sua aplicação, mas cuidado para não abusar muito, porque isto pode aumentar consideravelmente a quantidade de memória que sua aplicação está utilizando.

A configuração ideal para cada aplicação, cabe a você descobrir. Até a próxima.

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