Рубрики
Без рубрики

Копание в кэше запросов гибернации

Я уже давно не пользуюсь гибернацией и еще дольше не писал об этом в блогах. Недавно я.. Помечены как java, hibernate, cache, querycache.

Я уже давно не использую Hibernate, и я не писал об этом в блоге еще дольше. Недавно я работал над сообщением в блоге в контексте моей работы по настройке кэша evergreen . Когда я кодировал демо-версию, я столкнулся с некоторой проблемой, связанной с кешем запросов в режиме гибернации: он работал не так, как я ожидал. Наконец, через некоторое время мне удалось устранить проблему.

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

Кэши сущностей

Hibernate предлагает различные типы кэшей для разных объектов. Главными из них являются организации :

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

Hibernate предлагает двухуровневый кэш сущностей, невообразимо названный Кэшем уровня 1 и кэшем уровня 2.

  • Кэш уровня 1:

    L1C включен по умолчанию, и отключить его AFAIK невозможно. Он автоматически управляется объектом Session . Жизненный цикл кэша привязан к жизненному циклу сеанса.

    В стандартных веб-приложениях для каждого HTTP-запроса открывается спящий режим Session . Это означает, что каждый новый запрос начинается с холодного кэша. Следовательно, данные необходимо перезагрузить из базы данных.

  • Кэш уровня 2:

    L2C должен быть включен явно. Он основан на стороннем решении для кэширования например, EHCache, Cache, Hazelcast и т.д. Hibernate предлагает SPI для поставщиков, которые будут использоваться реализациями для взаимодействия с платформой. Возможности кэша зависят от реализации т.е. формат хранения, независимо от того, распределяются данные или нет, и т.д.

    Управление L2C осуществляется через SessionFactory : в веб-приложениях при запуске инициализируется один экземпляр. Это означает, что L2C, в отличие от L1C, может использоваться (и используется) для нескольких HTTP-запросов. +

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

Кэши сущностей используются в двух разных случаях:

  1. Когда объект загружается по его первичному ключу . Это тот случай, когда вызывается метод EntityManager.find(Class clazz, Object PrimaryKey) . С Spring Data JPA последний оборачивается реализацией метода CrudRepository.findById(ID id) .
  2. Когда любой другой метод EntityManager генерирует запрос SELECT . Это происходит во многих случаях например, с помощью любого из методов createXXX Query() или при использовании более безопасного для типов CriteriaBuilder . С Spring Data JPA это относится к любому из пользовательских методов, добавленных в один из репозиториев JPA.

Объекты, которые загружаются – в 1 st случае и запрашиваются – во 2 nd случае, сохраняются в кэше. Однако только в случае 1 st объекты считываются из кэша. Рассмотрим следующий пример:

@Entity
@Cache(region = "entities", usage = READ_WRITE)
class Thing(@Id val id: Long, val text: String)

interface ThingRepository : JpaRepository

@SpringBootApplication
class Sample {

    @Bean
    fun init(repo: ThingRepository) = CommandLineRunner() {
        repo.findAll()                                       // 1
        repo.findById(1L)                                    // 2
    }
}
  1. Все объекты загружаются из базы данных и сохраняются в кэше
  2. Объект с PK 1L будет загружен из кэша – если он существует

Теперь давайте изменим реализацию функции init() :

@Bean
fun clr(repo: ThingRepository) = CommandLineRunner() {
    repo.findAll()                                          // 1
    repo.findAll()                                          // 2
}
  1. Все объекты загружаются из базы данных и сохраняются в кэше
  2. Поскольку все объекты находятся в кэше, он не используется: объекты по-прежнему загружаются из базы данных

Кэш Запросов

На самом деле можно прочитать кэшированные результаты из общих SELECT запросов, 2 nd случай выше. Для этого требуется дополнительный кэш, кэш запросов . Включение кэша запросов – это двухэтапный процесс:

  1. Включите надлежащий кэш запросов.

    Например, в Spring Boot просто добавьте следующее в application.yml/|:

    Включите кэш запросов для каждого запроса, который необходимо кэшировать:
  2. С Spring Data JPA каждый метод запроса должен быть аннотирован с помощью

    @QueryHints(QueryHint(name,)) . Если метод не является пользовательским т.е. он уже предоставлен родительским JpaRepository например find All() , он должен быть переопределен, а переопределяющий метод аннотирован:

С помощью того же кода, что и выше, результаты будут возвращены из кэша, а доступ к базе данных невозможен:

@Bean
fun clr(repo: ThingRepository) = CommandLineRunner() {
    repo.findAll()                                          // 1
    repo.findAll()                                          // 2
}
  1. Все объекты загружаются из базы данных и сохраняются в кэше
  2. Объекты будут возвращены из кэша

Пример демонстрационного проекта

Я создал простой Spring Boot демонстрационный проект , используя Spring Data JPA и Spring Shell. Включены как L2C, так и Кэш запросов, а также статистика гибернации. Проект предлагает несколько команд:

  • сущности считывает все сущности из базы данных с помощью |/find All() способ кэш()
  • отображает содержимое L2C кэш запросов()
  • отображает содержимое кэша запросов

Давайте использовать их по порядку:

  1. После запуска L2C пуст:

  2. Кэш запросов также пуст:

  3. Давайте теперь загрузим объекты, используя Метод findAll() .

  4. Давайте убедимся, что L2C теперь заполнен:

  5. Это также относится и к кешу запросов:

    Обратите внимание, что в кэше запросов хранятся не сами объекты, а только их первичные ключи. Затем объекты загружаются из L2C.

  6. Поведение кэширования можно подтвердить, снова вызвав команду entities и просмотрев статистику гибернации:

    В нем упоминаются 4 попадания в кэш: одно для кэша запросов, а остальные 3 для L2C.

Вывод

Кэширование – это компромисс: оно повышает производительность, признавая, что кэшированные данные могут быть устаревшими. Когда сам кэш является единственным путем обновления базовой базы данных, риск устаревания данных равен нулю.

AL 2C может возвращать отдельные объекты из кэша; кэш запросов позволяет возвращать их массово. Подумайте об использовании последнего вместе с первым в ваших проектах для мгновенного повышения производительности.

Признание:

Спасибо моему другу Vlad “Vladuts” Mihalcea за его помощь в рецензировании этого поста.

Чтобы идти дальше:

Первоначально опубликовано на Фанат Java 5 июля th , 2020

Оригинал: “https://dev.to/nfrankel/digging-into-hibernate-s-query-cache-42hd”