Введение
Кэширование это возможность предоставляемая ORM фреймворками, которая помогает пользователям достичь высокую скорость, и в то же время помогает самим фреймворкам уменьшить количество обращений к БД. Hibernate достигает вторую цель вводя кэш первого уровня.
Кэш первого уровня в Hibernate включен по умолчанию и Вам не нужно ничего предпринимать, чтобы включить его. Даже выключить его не удастся. На самом деле, в роли кэша в сессии выступает Persistence Context. В приложении Hibernate, мы говорим что одна сессия имеет один внутренний persistence context. В JPA приложении, EntityManager имеет так же свой persistence context.
Persistence контекст полезен по нескольким причинам:
- Hibernate может делать автоматические dirty checking и транзакции
- Hibernate может использовать persistence контекст как кэш первого уровня
- Hibernate может гарантировать уникальность Java-объекта в масштабе сессии
- Слой persistence не уязвим к переполнениям стека в случае зацикленных ссылок в графе объектов
- Никогда не может быть противоречивых представлений одной и той же строки в БД в конце единицы работы
- Изменения совершенные в едином persistence контексте всегда видны сразу для всего остального исполняемого кода внутри persistence контекста и его единицы работы (гарантируются повторные чтения).
Легче понимать кэш первого уровня, если мы поймем тот факт, что он ассоциирован с объектом Session. Как мы знаем объект сессии создается по запросу от SessionFactory и кэш уничтожается в тот момент, когда сессия закрывается. Кэш первого уровня также называется Кэшем уровня сессии, означая что он работает для сессии только, в случае если один и тот же запрос будет выполнен два или более раз в одной сессии он выдаст данные из БД только для первого запроса и вернет те же самые данные из кэша для всех входящих подобных запросов.
Как работает кэширование первого уровня?
Кэш первого уровня связан с сессией только, когда запрос выполняется первый раз он выдает данные из БД и сохраняет результат запроса в кэш. Данные сохраненные в кэше при первом запросе будут выдываться и для всех входящих подобных запросов, пока сессия жива.
Как только сессия закроется весь кэш первого уровня будет очищен. Мы можем удалить частично загруженную сущность из кэша при помощи метода evict() и весь кэш может быть очищен используя метод clear(). Как только кэш очистится при вызове одного из двух методов свыше, он будет делать запрос к БД снова для нового вызова даже если сессия еще не закрыта.
Пример:
package ru.akorsa.sample; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import ru.akorsa.models.Employee; import ru.akorsa.utils.HibernateUtils; public class CachingImpl { public static void main(String[] args) { SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); // create an employee object to persist in DB Employee employee = new Employee("eddy", "smith", "eddy@akorsa.ru", "9898787676"); // lets save few data in Employee table Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); session.save(employee); transaction.commit(); session.close(); System.out.println("****Data creation completed****"); Session session1 = sessionFactory.openSession(); System.out.println("****Created Session 1****"); transaction = session1.beginTransaction(); employee = (Employee) session1.load(Employee.class, 1l); System.out.println(employee.getFirstName()); employee = (Employee) session1.load(Employee.class, 1l); System.out.println(employee.getFirstName()); employee = (Employee) session1.load(Employee.class, 1l); System.out.println(employee.getFirstName()); transaction.commit(); session1.close(); System.out.println("****Closed Session 1****"); Session session2 = sessionFactory.openSession(); System.out.println("****Created Session 2****"); transaction = session2.beginTransaction(); employee = (Employee) session2.load(Employee.class, 1l); System.out.println(employee.getFirstName()); transaction.commit(); session2.close(); System.out.println("****Closed Session 2****"); } }
Результат:
Вы можете видеть, что в сессии под номер 1 мы запросили сущность три раза, но запрос в БД был выполнен только один раз и для второго и третьего запроса были получены данные сохраненные в кэше. Мы закрываем сессию 1 и открываем вторую сессию, в этот раз мы запросили эту же сущность из БД, но был сделан запрос прямо в БД, потому что кэшированные данные очистились, когда сессия 1 закрылась.
Как работают методы evict() и clear() в Hibernate
Мы не можем выключить кэш первого уровня в Hibernate, но мы можем удалить некоторую конкретную сущность из кэша с помощью evict() и весь кэш с помощью clear(). Пример ниже:
package ru.akorsa.sample; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import ru.akorsa.models.Employee; import ru.akorsa.utils.HibernateUtils; public class EvictImpl { public static void main(String[] args) { SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); // create an employee object to persist in DB Employee employee = new Employee("eddy", "smith", "eddy@beingjavaguys.com", "9898787676"); // lets save few data in Employee table Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); session.save(employee); transaction.commit(); session.close(); System.out.println("****Data creation completed****"); Session session1 = sessionFactory.openSession(); System.out.println("****Created Session****"); transaction = session1.beginTransaction(); employee = (Employee) session1.load(Employee.class, 1l); System.out.println(employee.getFirstName()); employee = (Employee) session1.load(Employee.class, 1l); System.out.println(employee.getFirstName()); // removed employee entity from the cache session1.evict(employee); System.out.println("****removed employee entity from the cache****"); employee = (Employee) session1.load(Employee.class, 1l); System.out.println(employee.getFirstName()); transaction.commit(); session1.close(); System.out.println("****Closed Session****"); } }
Результат: