Nunca escreva sua solução de cache

Outra forma de agilizar o trabalho com o banco de dados é armazenar em cache os objetos que já solicitamos anteriormente.

Importante! Nunca escreva sua própria solução de cache. Esta tarefa tem tantas armadilhas com as quais você nunca sonhou.

Problema 1 - descarga de cache . Às vezes, ocorrem eventos quando um objeto precisa ser removido do cache ou atualizado no cache. A única maneira de fazer isso com competência é passar todas as solicitações ao banco de dados por meio do mecanismo de cache. Caso contrário, toda vez você terá que informar explicitamente ao cache quais objetos nele devem ser excluídos ou atualizados.

Problema 2 - falta de memória . O armazenamento em cache parece uma ótima ideia até você descobrir que os objetos na memória ocupam muito espaço. Você precisa de dezenas de gigabytes adicionais de memória para que o cache do aplicativo do servidor funcione com eficiência.

E como sempre há falta de memória, é necessária uma estratégia eficaz para excluir objetos do cache. Isso é um pouco semelhante ao coletor de lixo em Java. E como você se lembra, por décadas as melhores mentes inventaram várias maneiras de marcar objetos por gerações, etc.

Problema 3 - estratégias diferentes . Como mostra a prática, diferentes estratégias de armazenamento e atualização no cache são eficazes para diferentes objetos. Um sistema de cache eficiente não pode fazer apenas uma estratégia para todos os objetos.

Problema 4 - Armazenamento eficiente de arquivos . Você não pode simplesmente armazenar objetos no cache. Os objetos muitas vezes contêm referências a outros objetos e assim por diante. Nesse ritmo, você não precisará de um coletor de lixo: ele simplesmente não terá nada para remover.

Portanto, em vez de armazenar os próprios objetos, às vezes é muito mais eficiente armazenar os valores de seus campos primitivos. E sistemas para construir objetos rapidamente com base neles.

Como resultado, você obterá um DBMS virtual inteiro na memória, que deve funcionar rapidamente e consumir pouca memória.

Cache do banco de dados

Além de armazenar em cache diretamente em um programa Java, o armazenamento em cache geralmente é organizado diretamente no banco de dados.

Existem quatro grandes abordagens:

A primeira abordagem é desnormalizar o banco de dados . O servidor SQL armazena dados na memória de maneira diferente de como são armazenados nas tabelas.

Quando os dados são armazenados no disco em tabelas, muitas vezes os desenvolvedores tentam evitar ao máximo a duplicação de dados - esse processo é chamado de normalização do banco de dados. Assim, para agilizar o trabalho com dados na memória, é realizado o processo inverso - desnormalização do banco de dados. Várias tabelas relacionadas já podem ser armazenadas de forma combinada - na forma de tabelas enormes, etc.

A segunda abordagem é o cache de consulta . E consultar resultados.

O DBMS vê que muitas vezes as mesmas solicitações ou solicitações semelhantes chegam a ele. Em seguida, ele simplesmente começa a armazenar em cache essas solicitações e suas respostas. Mas, ao mesmo tempo, você precisa garantir que as linhas que foram alteradas no banco de dados sejam removidas do cache em tempo hábil.

Essa abordagem pode ser muito eficaz com um ser humano que pode analisar consultas e ajudar o DBMS a descobrir a melhor forma de armazená-las em cache.

A terceira abordagem é um banco de dados na memória .

Outra abordagem comumente usada. Outro banco de dados é colocado entre o servidor e o SGBD, que armazena todos os seus dados apenas na memória. Também é chamado In-Memory-DB. Se você tiver muitos servidores diferentes acessando o mesmo banco de dados, usando o In-Memory-DB, poderá organizar o cache com base no tipo de um servidor específico.

Exemplo:

Aproxime-se do cluster de 4 bancos de dados . Várias bases somente leitura.

Outra solução é usar um cluster: vários SGBDs do mesmo tipo contêm dados idênticos. Ao mesmo tempo, você pode ler dados de todos os bancos de dados e gravar em apenas um. Que é então sincronizado com o restante dos bancos de dados.

Esta é uma solução muito boa porque é fácil de configurar e funciona na prática. Normalmente, para uma solicitação ao banco de dados para alterar dados, 10 a 100 solicitações de leitura de dados chegam a ele.

Tipos de cache no Hibernate

O Hibernate suporta três níveis de cache:

  • Cache no nível da sessão (Sessão)
  • Cache no nível SessionFactory
  • Solicitações de cache (e seus resultados)

Você pode tentar representar este sistema na forma de tal figura:

O tipo mais simples de cache (também chamado de cache de primeiro nível ) é implementado no nível de sessão do Hibernate. O Hibernate sempre usa esse cache por padrão e não pode ser desabilitado .

Consideremos imediatamente o seguinte exemplo:

Employee director1 = session.get(Employee.class, 4);
Employee director2 = session.get(Employee.class, 4);

assertTrue(director1 == director2);

Pode parecer que duas consultas ao banco de dados serão executadas aqui, mas não é assim. Após a primeira solicitação ao banco de dados, o objeto Funcionário será armazenado em cache. E se você consultar o objeto novamente na mesma sessão, o Hibernate retornará o mesmo objeto Java.

O mesmo objeto significa que mesmo as referências de objeto serão idênticas. É realmente o mesmo objeto.

Os métodos save() , update() , saveOrUpdate() , load() , get() , list() , iterate() e scroll() sempre usarão o cache de primeiro nível. Na verdade, não há mais nada a acrescentar.

Cache de segundo nível

Se o cache de primeiro nível estiver vinculado ao objeto de sessão, o cache de segundo nível será vinculado ao objeto de sessão.SessionFactory. O que significa que a visibilidade dos objetos neste cache é muito maior do que no cache de primeiro nível.

Exemplo:

Session session = factory.openSession();
Employee director1 = session.get(Employee.class, 4);
session.close();

Session session = factory.openSession();
Employee director2 = session.get(Employee.class, 4);
session.close();

assertTrue(director1 != director2);
assertTrue(director1.equals(director2));

Neste exemplo, serão feitas duas consultas ao banco de dados. O Hibernate retornará objetos idênticos, mas não será o mesmo objeto - eles terão referências diferentes.

O cache de segundo nível está desabilitado por padrão . Portanto, temos duas consultas ao banco de dados em vez de uma.

Para ativá-lo, você precisa escrever as seguintes linhas no arquivo hibernate.cfg.xml:

<property name="hibernate.cache.provider_class" value="net.sf.ehcache.hibernate.SingletEhCacheProvider"/>
<property name="hibernate.cache.use_second_level_cache" value="true"/>

Depois de habilitar o cache de segundo nível, o comportamento do Hibernate mudará um pouco:

Session session = factory.openSession();
Employee director1 = session.get(Employee.class, 4);
session.close();

Session session = factory.openSession();
Employee director2 = session.get(Employee.class, 4);
session.close();

assertTrue(director1 == director2);

Somente após todas essas manipulações o cache de segundo nível será habilitado, e no exemplo acima será executada apenas uma consulta ao banco de dados.