Cache de segundo nível de hibernação
1. Visão geral
Uma das vantagens das camadas de abstração do banco de dados, como estruturas ORM (mapeamento objeto-relacional), são seusability to transparently cache data recuperados do armazenamento subjacente. Isso ajuda a eliminar os custos de acesso ao banco de dados para dados acessados com frequência.
Os ganhos de desempenho podem ser significativos se as taxas de leitura / gravação do conteúdo em cache forem altas, especialmente para entidades que consistem em grandes gráficos de objetos.
Neste artigo, exploramos o cache de segundo nível do Hibernate.
Explicamos alguns conceitos básicos e, como sempre, ilustramos tudo com exemplos simples. Usamos o JPA e voltamos à API nativa do Hibernate apenas para os recursos que não são padronizados no JPA.
2. O que é um cache de segundo nível?
Como a maioria das outras estruturas ORM totalmente equipadas, o Hibernate tem o conceito de cache de primeiro nível. É um cache com escopo de sessão que garante que cada instância da entidade seja carregada apenas uma vez no contexto persistente.
Depois que a sessão é fechada, o cache de primeiro nível também é encerrado. Isso é realmente desejável, pois permite que sessões simultâneas trabalhem com instâncias de entidade isoladas umas das outras.
Por outro lado, o cache de segundo nível tem escopoSessionFactory, o que significa que é compartilhado por todas as sessões criadas com a mesma fábrica de sessão. Quando uma instância de entidade é pesquisada por seu id (seja pela lógica do aplicativo ou pelo Hibernate internamente,e.g. quando ele carrega associações para essa entidade de outras entidades), e se o cache de segundo nível estiver habilitado para essa entidade, o seguinte acontece:
-
Se uma instância já estiver presente no cache de primeiro nível, ela será retornada a partir daí
-
Se uma instância não for encontrada no cache de primeiro nível e o estado da instância correspondente for armazenado em cache no cache de segundo nível, os dados serão buscados a partir daí e uma instância será montada e retornada
-
Caso contrário, os dados necessários são carregados do banco de dados e uma instância é montada e retornada
Depois que a instância é armazenada no contexto de persistência (cache de primeiro nível), ela é retornada de lá em todas as chamadas subseqüentes na mesma sessão até que a sessão seja fechada ou a instância seja removida manualmente do contexto de persistência. Além disso, o estado da instância carregado é armazenado no cache L2, se ainda não estiver lá.
3. Fábrica Regional
O cache de segundo nível do Hibernate foi projetado para desconhecer o provedor de cache real usado. O Hibernate só precisa ser fornecido com uma implementação da interfaceorg.hibernate.cache.spi.RegionFactory que encapsula todos os detalhes específicos dos provedores de cache reais. Basicamente, ele atua como uma ponte entre o Hibernate e os provedores de cache.
Neste artigo,we use Ehcache as a cache provider, que é um cache maduro e amplamente usado. Você pode escolher qualquer outro provedor, é claro, desde que haja uma implementação deRegionFactory para ele.
Adicionamos a implementação de fábrica da região Ehcache ao caminho de classe com a seguinte dependência do Maven:
org.hibernate
hibernate-ehcache
5.2.2.Final
Take a look here para a versão mais recente dehibernate-ehcache. No entanto, certifique-se de que a versãohibernate-ehcache é igual à versão do Hibernate que você usa em seu projeto,e.g. se você usarhibernate-ehcache 5.2.2.Final como neste exemplo, então a versão do Hibernate também deve ser5.2.2.Final.
O artefatohibernate-ehcache tem uma dependência da própria implementação do Ehcache, que é, portanto, transitivamente incluída no classpath.
4. Habilitando o cache de segundo nível
Com as duas propriedades a seguir, informamos ao Hibernate que o cache L2 está ativado e fornecemos o nome da classe de fábrica da região:
hibernate.cache.use_second_level_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
Por exemplo, empersistence.xml seria semelhante a:
...
...
Para desabilitar o cache de segundo nível (para propósitos de depuração, por exemplo), apenas defina a propriedadehibernate.cache.use_second_level_cache como false.
5. Tornando uma entidade em cache
Paramake an entity eligible for second-level caching, nós anotamos com a anotação@org.hibernate.annotations.Cache específica do Hibernate e especificamos umcache concurrency strategy.
Alguns desenvolvedores consideram uma boa convenção adicionar a anotação@javax.persistence.Cacheable padrão também (embora não seja exigida pelo Hibernate), portanto, uma implementação de classe de entidade pode ser assim:
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Foo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "ID")
private long id;
@Column(name = "NAME")
private String name;
// getters and setters
}
Para cada classe de entidade, o Hibernate usará uma região de cache separada para armazenar o estado das instâncias dessa classe. O nome da região é o nome completo da classe.
Por exemplo, as instâncias deFoo são armazenadas em um cache chamadocom.example.hibernate.cache.model.Foo no Ehcache.
Para verificar se o cache está funcionando, podemos escrever um teste rápido como este:
Foo foo = new Foo();
fooService.create(foo);
fooService.findOne(foo.getId());
int size = CacheManager.ALL_CACHE_MANAGERS.get(0)
.getCache("com.example.hibernate.cache.model.Foo").getSize();
assertThat(size, greaterThan(0));
Aqui, usamos a API Ehcache diretamente para verificar se o cachecom.example.hibernate.cache.model.Foo não está vazio depois de carregarmos uma instânciaFoo.
Você também pode habilitar o log de SQL gerado pelo Hibernate e invocarfooService.findOne(foo.getId()) várias vezes no teste para verificar se a instruçãoselect para carregarFoo é impressa apenas uma vez (a primeira vez), o que significa que nas chamadas subsequentes, a instância da entidade é buscada no cache.
6. Estratégia de simultaneidade de cache
Com base nos casos de uso, podemos escolher uma das seguintes estratégias de simultaneidade de cache:
-
READ_ONLY: usado apenas para entidades que nunca mudam (uma exceção é lançada se for feita uma tentativa de atualizar tal entidade). É muito simples e com bom desempenho. Muito adequado para alguns dados de referência estáticos que não mudam
-
NONSTRICT_READ_WRITE: o cache é atualizado depois que uma transação que alterou os dados afetados foi confirmada. Portanto, a consistência forte não é garantida e há uma pequena janela de tempo na qual dados obsoletos podem ser obtidos do cache. Esse tipo de estratégia é adequado para casos de uso que podem tolerar consistência eventual
-
READ_WRITE: Esta estratégia garante a consistência forte que atinge usando bloqueios 'soft': quando uma entidade em cache é atualizada, um bloqueio suave é armazenado no cache para essa entidade também, que é liberado após a transação ser confirmada . Todas as transações simultâneas que acessam entradas com bloqueio automático buscarão os dados correspondentes diretamente do banco de dados
-
TRANSACTIONAL: as alterações do cache são feitas em transações XA distribuídas. Uma alteração em uma entidade em cache é confirmada ou revertida no banco de dados e no cache na mesma transação XA
7. Gerenciamento de cache
Se as políticas de expiração e despejo não forem definidas, o cache poderá crescer indefinidamente e, eventualmente, consumir toda a memória disponível. Na maioria dos casos, o Hibernate deixa tarefas de gerenciamento de cache como essas para os provedores de cache, pois elas são realmente específicas para cada implementação de cache.
Por exemplo, poderíamos definir a seguinte configuração Ehcache para limitar o número máximo de instânciasFoo em cache a 1000:
8. Cache de coleção
As coleções não são armazenadas em cache por padrão, e precisamos marcá-las explicitamente como armazenáveis em cache. Por exemplo:
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Foo {
...
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@OneToMany
private Collection bars;
// getters and setters
}
9. Representação Interna do Estado em Cache
As entidades não são armazenadas no cache de segundo nível como instâncias Java, mas em seu estado desmontado (hidratado):
-
O ID (chave primária) não é armazenado (é armazenado como parte da chave do cache)
-
Propriedades transitórias não são armazenadas
-
As coleções não são armazenadas (veja abaixo para mais detalhes)
-
Os valores da propriedade que não são de associação são armazenados em sua forma original
-
Apenas id (chave estrangeira) é armazenado para associaçõesToOne
Isso mostra o design geral do cache de segundo nível do Hibernate, no qual o modelo de cache reflete o modelo relacional subjacente, que economiza espaço e facilita a manutenção dos dois sincronizados.
9.1. Representação interna de coleções em cache
Já mencionamos que temos que indicar explicitamente que uma coleção (OneToMany ouManyToMany associação) pode ser armazenada em cache, caso contrário, não é armazenada em cache.
Na verdade, o Hibernate armazena coleções em regiões de cache separadas, uma para cada coleção. O nome da região é um nome de classe totalmente qualificado mais o nome da propriedade da coleção, por exemplo:com.example.hibernate.cache.model.Foo.bars. Isso nos dá a flexibilidade de definir parâmetros de cache separados para coleções, política de despejo / expiraçãoe.g..
Além disso, é importante mencionar que apenas os IDs das entidades contidas em uma coleção são armazenados em cache para cada entrada da coleção, o que significa que, na maioria dos casos, é uma boa idéia tornar as entidades contidas em cache também.
10. Invalidação de cache para consultas de estilo HQL DML e consultas nativas
Quando se trata de HQL no estilo DML (insert,updateedelete instruções HQL), o Hibernate é capaz de determinar quais entidades são afetadas por tais operações:
entityManager.createQuery("update Foo set … where …").executeUpdate();
Nesse caso, todas as instâncias do Foo são despejadas do cache L2, enquanto outro conteúdo em cache permanece inalterado.
No entanto, quando se trata de instruções SQL DML nativas, o Hibernate não consegue adivinhar o que está sendo atualizado, portanto invalida todo o cache de segundo nível:
session.createNativeQuery("update FOO set … where …").executeUpdate();
Provavelmente não é isso que você quer! A solução é dizer ao Hibernate quais entidades são afetadas por instruções DML nativas, de forma que ele possa remover apenas as entradas relacionadas às entidadesFoo:
Query nativeQuery = entityManager.createNativeQuery("update FOO set ... where ...");
nativeQuery.unwrap(org.hibernate.SQLQuery.class).addSynchronizedEntityClass(Foo.class);
nativeQuery.executeUpdate();
Também recuamos para a APISQLQuery nativa do Hibernate, pois esse recurso (ainda) não está definido no JPA.
Observe que o acima se aplica apenas a instruções DML (insert,update,deletee chamadas de função / procedimento nativas). As consultasselect nativas não invalidam o cache.
11. Cache de Consulta
Os resultados das consultas HQL também podem ser armazenados em cache. Isso é útil se você executar frequentemente uma consulta em entidades que raramente mudam.
Para habilitar o cache de consulta, defina o valor da propriedadehibernate.cache.use_query_cache paratrue:
hibernate.cache.use_query_cache=true
Então, para cada consulta, você deve indicar explicitamente que a consulta pode ser armazenada em cache (por meio de uma dica de consultaorg.hibernate.cacheable):
entityManager.createQuery("select f from Foo f")
.setHint("org.hibernate.cacheable", true)
.getResultList();
11.1. Práticas recomendadas de cache de consulta
Aqui estão algunsguidelines and best practices related to query caching:
-
Como é o caso das coleções, apenas os IDs das entidades retornadas como resultado de uma consulta armazenável em cache são armazenados em cache; portanto, é altamente recomendável que o cache de segundo nível seja ativado para essas entidades.
-
Há uma entrada de cache para cada combinação de valores de parâmetros de consulta (variáveis de ligação) para cada consulta, portanto, as consultas para as quais você espera muitas combinações diferentes de valores de parâmetros não são boas candidatas ao armazenamento em cache.
-
As consultas que envolvem classes de entidade para as quais há alterações frequentes no banco de dados também não são boas candidatas para armazenamento em cache, pois serão invalidadas sempre que houver uma alteração relacionada a qualquer entidade classificada participando da consulta, independentemente de as instâncias alteradas serem armazenado em cache como parte do resultado da consulta ou não.
-
Por padrão, todos os resultados do cache de consulta são armazenados na regiãoorg.hibernate.cache.internal.StandardQueryCache. Assim como no cache de entidade / coleção, você pode personalizar os parâmetros de cache para esta região para definir políticas de remoção e expiração de acordo com suas necessidades. Para cada consulta, você também pode especificar um nome de região personalizado para fornecer configurações diferentes para consultas diferentes.
-
Para todas as tabelas que são consultadas como parte das consultas armazenáveis em cache, o Hibernate mantém os timestamps da última atualização em uma região separada chamadaorg.hibernate.cache.spi.UpdateTimestampsCache. Estar ciente dessa região é muito importante se você usar o cache de consultas, porque o Hibernate a utiliza para verificar se os resultados da consulta em cache não são obsoletos. As entradas neste cache não devem ser despejadas / expiradas, desde que haja resultados da consulta em cache para as tabelas correspondentes nas regiões de resultados da consulta. É melhor desativar o despejo e a expiração automáticos para essa região de cache, pois ela não consome muita memória.
12. Conclusão
Neste artigo, vimos como configurar o cache de segundo nível do Hibernate. Vimos que é bastante fácil de configurar e usar, pois o Hibernate faz todo o trabalho pesado nos bastidores, tornando a utilização do cache de segundo nível transparente à lógica de negócios do aplicativo.
A implementação deste tutorial de cache de segundo nível do Hibernate está disponívelon Github. Este é um projeto baseado em Maven, portanto, deve ser fácil importar e executar como está.