Зачем нужен кэш запросов

Перепишем наш пример с получением сотрудников на HQL:

Employee director1 = session.createQuery("from Employee where id = 4").uniqueResult();
Employee director2 = session.createQuery("from Employee where id = 4").uniqueResult();

assertTrue(director1 != director2);

Результаты такого рода запросов не сохраняются кэшем ни первого, ни второго уровня.

Это как раз то место, где можно использовать кэш запросов. Он тоже по умолчанию отключен. Для включения нужно добавить следующую строку в конфигурационный файл:

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

Но это только половина решения. Кэш запросов мы включили, но нам нужно еще и указать, результаты каких запросов мы хотим кэшировать. Это нужно прописать в запросе Query:

Query query = session.createQuery("from Employee where id = 4");
query.setCacheable(true);
Employee director1 = query.uniqueResult();

Кэш запросов похож на кэш второго уровня. Но, в отличии от него, тут ключом к данным кэша выступает не идентификатор объекта, а совокупность параметров запроса. А сами данные — это идентификаторы объектов, соответствующих критериям запроса. Таким образом этот кэш рационально использовать с кэшем второго уровня.

Очистка кэша

Одна из важных задач при работе с кэшем — это следить за тем, чтобы кэшируемые объекты изменились, и удалять их из кэша (или обновлять). Hibernate делает это очень даже успешно. Иногда даже кажется, что он руководствуется правилом “в любой непонятной ситуации очищай кэш”.

Допустим, ты хочешь обновить данные пользователя через HQL:

Query query = session.createQuery("update Employee set name=’Alex’ where id = 4")
query. executeUpdate();

Hibernate не может точно знать, что в базе поменялось, но он знает, что ты меняешь объект типа Employee. Поэтому после выполнения этого запроса Hibernate удалит из своего кэша вообще все объекты типа Employee.

Но еще интересней работает NativeQuery:

Query nativeQuery = session.createNativeQuery("update employee set name=’Alex’ where id = 4")
nativeQuery.executeUpdate();

Выполнился родной SQL-запрос к базе данных. А значит, в базе данных что-то поменялось — запрос-то вызван в методе executeUpdate(). Поэтому в этом случае Hibernate немного перестрахуется и удалит из своего кэша вообще все объекты всех типов.

Как тебе такое? Ты вызываешь безобидный запрос, а Hibernate в ответ стирает все данные из кэша! Это конечно лучше, чем если бы он хранил у себя объекты, которые отличаются от базы, но такое!

Поэтому создатели Hibernate быстро придумали, как помочь Hibernate в этом случае. Ты можешь указать ему, какой тип сущностей нужно удалить из кэша:

Query nativeQuery = session.createNativeQuery("update employee set name=’Alex’ where id = 4");
nativeQuery.unwrap(org.hibernate.SQLQuery.class).addSynchronizedEntityClass(Employee.class);
nativeQuery.executeUpdate();
Замечание. Нативные select-запросы не сбрасывают кэш, только insert, update, delete, вызовы процедур и т. п.

Ручная очистка кэша

По определенным причинам ты можешь захотеть удалить объект из кэша самостоятельно. Сделать это можно разными способами.

Примечание. Объекты в кэше сохраняются группами, которые называются регионами. По умолчанию имя региона совпадает с именем класса. Поэтому если у тебя есть объекты типа com.javarush.Employee, то все они будут сохранены в группу (регион) с именем “com.javarush.employee”.

Если ты хочешь получить доступ к кэшу и что-то с ним сделать, то это можно сделать с помощью объекта SessionFactory и метода getCache():

session.getSessionFactory().getCache().evictQueryRegion("com.javarush.employee”);

Если ты хотчешь удалить данные из всех групп (регионов), то нужно выполнить такой запрос:

session.getSessionFactory().getCache().evictAllRegions();

Чтобы удалить один объект из кэша, нужно передать его имя (тип) и id. Сделать это можно двумя способами:

session.getSessionFactory().getCache().evictEntityData("Employee, 4);

session.getSessionFactory().getCache().evictEntityData(com.javarush.Employee.class, 4);

Также ты можешь проверить, содержится ли определенный объект в кэше:

session.getSessionFactory().getCache().containsEntity("Employee, 4);
session.getSessionFactory().getCache().containsEntity(com.javarush.Employee.class, 4);