[JPA] Melhorando a performance de aplicações JPA com cache de segundo nível.
Autor: Mauricio Marinho | Publicado: 2012-12-31T19:40:00-03:00



O cache de segundo nível é um
repositório local, gerenciado pelo mesmo mecanismo de persistencia
responsável pelo ORM da aplicação, para melhorar a performance da
mesma.



A intenção é melhorar a performance
reduzindo as conversações entre aplicação e banco de dados.



É suportado pela especificação JPA e
pode ser configurado para os seguintes modos de trabalho (Cache
Mode):



 
 
 

  ALL
  Todas as entidades declaradas na unidade de persistência irão
   para o cache.
 

  NONE
  Nenhuma entidade da unidade de persistência irá para o cache.
 

  ENABLE_SELECTIVE
  Habilita o cache para entidades que forem declaradas para tal
   com a anotação @Cacheable.
 

  DISABLE_SELECTIVE
  Habilita o cache para todas as entidades da unidade de
   persistência, exceto para aquelas que estiverem anotadas com
   @Cacheable(false).
 

  UNSPECIFIED
  Adota o comportamento default do mecanismo de persistência JPA
   utilizado.
 




Um problema comum do uso do cache de
segundo nível é quando os dados mudam no banco mas não mudam no
cache. É o que se chama de stale read. Stale reads podem ser
resolvidos com a estratégia adequada de configuração do modo de
trabalho do cache e outras configurações possíveis. Por isso, é
muito importante conhecer profundamente o cache de segundo nível do
mecanismo de persistência que você pretende adotar.



No modo ENEABLE_SELECTIVE, as
subclasses das entidades anotadas com @Cacheable também serão
cacheadas a menos que a anotação seja sobrescrita com
@Cacheable(false).



Especificando as configurações de
cache mode para melhorar a performance.



Para ajustar o cache mode para uma
unidade de persistencia, especifique um dos modos possíveis como valor do
elemento shared-cache-mode no persistence.xml.


&lt;persistence-unit name="examplePU" transaction-type="JTA"&gt;
  &lt;provider&gt;org.eclipse.persistence.jpa.PersistenceProvider&lt;/provider&gt;
  &lt;jta-data-source&gt;jdbc/__default&lt;/jta-data-source&gt;
  &lt;shared-cache-mode&gt;DISABLE_SELECTIVE&lt;/shared-cache-mode&gt;
&lt;/persistence-unit&gt;



Ou então, programaticamente:


EntityManagerFactor emf = 
    Persistence.createEntityManagerFactory(
        "myExamplePU", new Properties().add(
            "javax.persistence.sharedCache.mode", "ENABLE_SELECTIVE"));



OBS: Se o mecanismo de persistencia
adotado não suportar cache de segundo nível, as configurações
acima não produzirão qualquer efeito na aplicação.



Configurando cache retrieval e store
modes.



Se o cache de segundo nível estiver
habilitado na unidade de persistência pela configuração do shared
cache mode, o comportamento do cache de segundo nível poderá ser
modificado configurando as propriedades
javax.persistence.cache.retrieveMode e
javax.persistence.cache.stroreMode. Estas propriedades podem ser
configuradas no nível do contexto de persistência, passando o nome e
o valor da propriedade para o método EntityManager.setProperty, ou
configurando uma operação per-EntityManager, ou seja, por entidade (EntityManager.find ou
EntityManager.refresh) ou no nível per-query, ou seja, por query.



Cache Retrieval Mode



O modo de recuperação de dados a
partir do cache, configurado pela propriedade
javax.persistence.retrieveMode, controla como os dados são lidos do
cache por chamadas ao método EntityManager.find e a partir de
queries.



A propriedade retrieveMode pode ser
configurada com uma das constantes definidas pelo enum
javax.persistence.CacheRetrieveMode (USE que é o default ou BYPASS).
Quando configurada para USE, os dados são recuperados a partir do
cache, se disponíveis. Se o dado não estiver no cache, o mecanismo
de persistência irá buscá-lo no banco. Quando configurada com
BYPASS, o mecanismo de persistência ignora o cache e os dados são
recuperados diretamente do banco de dados.



Cache Store Mode 




O modo de salva dos dados do cache,
configurado pela propriedade javax.presistence.storeMode, controla
como os dados serão salvos no cache.



A propriedade storeMode pode ser
configurada por uma das constantes definidas pelo enum
javax.persistence.CacheStoreMode, podendo ser USE, que é o default,
BYPASS ou REFRESH. Configurada como USE, o dado no cache é criado ou
atualizado na primeira vez que for lido do banco de dados ou gravado
(commited) no mesmo. Se o dado já estiver no cache, o modo USE não
irá forçar um refresh quando o dado for lido do banco.



Quando configurada para BYPASS, os
dados lidos ou gravados no banco não serão inseridos no cache. É
isso! O cache será imutável.



Quando configurada para REFRESH o dado
é salvo no cache toda vez que for lido ou gravado no banco. Se o
dado já estiver no cache, será atualizado (refreshed) no cache.



Configurando o Cache Retrieval ou o
Store Mode.



Para configurar o cache retrieval ou
store mode, basta chamar o método EntityManager.setProperty do
contexto de persistência e passar como parâmetro o par {nome, valor} da propriedade:


EntityManager em = ...;
em.setProperty("javax.persistence.cache.storeMode", "BYPASS");



Para configurar o cache retrieval ou store mode quando chamar os
métodos EntityManager.find ou EntityManager.refresh, primeiro crie
uma instância de Map&lt;String, Object&gt; e adicione os pares nome,
valor conforme a seguir:


EntityManager em = ...;
Map&lt;String, Object&gt; props = new HashMap&lt;String, Object&gt;();
props.put("javax.persistence.cache.retrieveMode", "BYPASS");
String personPK = ...;
Person person = em.find(Person.class, personPK, props);




OBS: O cache retrieve mode será ignorado na chamada do método
EntityManager.refresh, pois as chamadas para refresh resultam sempre
em dados lidos do banco e nunca do cache.



Para configurar o retrieval ou store
mode quando for usar queries, chame o método Query.setHint ou o
método TypedQuerie.setHint, dependendo do tipo de query:


EntityManager em = ...;
CriteriaQuery&lt;Person&gt; cq = ...;
TypedQuery&lt;Person&gt; q = em.createQuery(cq);
q.setHint("javax.persistence.cache.storeMode", "REFRESH");
...




OBS: Configurar o store ou o retrieval mode em uma query ou chamando
os métodos EntityManager.find ou EntityManager.refresh sobrescreve
as configurações do entity manager.





Controlando o cache de segundo nível
programaticamente.



A interface javax.presistence.Cache
define métodos para interagir com o cache de segundo nível
programaticamente. A interface Cache define métodos para checar se
uma entidade particular tem dados salvos no cache, para remover uma
entidade particular do cache, para remover todas as instâncias
(inclusive de subclasses) de qualquer classe de entidade do cache e
para limpar o cache de todos os dados das entidades.



OBS: Se o cache de segundo nível for
desabilitado, chamadas aos métodos da interface Cache não terão
qualquer efeito sobre a aplicação, exceto para o método contains,
que sempre irá retornar false.



Checando se os dados de uma entidade
estão no cache.



Chame o método Cache.contains para
achar ou simplesmente para verificar se uma entidade está no cache
de segundo nível. Este método retornará true se os dados da
entidade estiverem em cache e false em caso contrário.


EntityManager em = ...;
Cache cache = em.getEntityManagerFactory().getCache();
String personPK = ...;
if (cache.contains(Person.class, personPK)) {
  // the data is cached
} else {
  // the data is NOT cached
}


Removendo uma entidade do cache.


Chame um dos métodos Cache.evict para remover uma entidade em
particular ou todas as entidades de um dado tipo do cache de segundo
nível.

Para remover uma entidade em particular chame Cache.evict passando
a classe da entidade e o Id (chave primária).


EntityManager em = ...;
Cache cache = em.getEntityManagerFactory().getCache();
String personPK = ...;
cache.evict(Person.class, personPK);

Para remover todas as instâncias de uma classe de entidade,
incluindo suas subclasses, chame evict e especifique a classe:

EntityManager em = ...;
Cache cache = em.getEntityManagerFactory().getCache();
cache.evict(Person.class);

Todas as instâncias da classe Person serão removidas do cache. Se
Person tiver subclasse, chame o método evictAll para remover suas
instâncias também.


Removendo todos os dados do cache. 



Chamar o método Cache.evictAll irá limpar completamente o cache
de segundo nível:


EntityManager em = ...;
Cache cache = em.getEntityManagerFactory().getCache();
cache.evictAll();

E é isso! Este post é o resultado da tradução, com poucas
adaptações, do “The Java EE 6 Tutorial,  Part VI Persistence, Chapter 38 Improving the Performance of Java Persistence APIApplications by Setting a Second-Level Cache”.


Fonte: http://docs.oracle.com/javaee/6/tutorial/doc/gkjjj.html
Tradução: Mauricio da Silva Marinho
Ao copiar, dê crédito ao autor.