segunda-feira, 31 de dezembro de 2012

[JPA] Melhorando a performance de aplicações JPA com cache de segundo nível.


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.

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

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<String, Object> e adicione os pares nome, valor conforme a seguir:

EntityManager em = ...;
Map<String, Object> props = new HashMap<String, Object>();
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<Person> cq = ...;
TypedQuery<Person> 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.

Um comentário:

Mauricio disse...
Este comentário foi removido por um administrador do blog.