Для чего нужна аннотация cacheable
Кэширование в Hibernate
В статье о поддержке пользовательских типов в Hibernate упоминается о поддержке кэширования. В этой статье я постараюсь рассказать о кэшровании подробнее.
Идея кэширования (не только в Hibernate) основывается на мнении, что из всех данных, доступных для обработки, работа ведётся только над некоторым небольшим набором и если ускорить доступ к этому набору, то в среднем программа будет работать быстрее. Говоря конкретно о Hibernate, доступ к базе занимает на порядке больше времени, чем доступ к объекту в памяти JVM. И поэтому, если какое-то время хранить в памяти загруженные из БД объекты, то при их повторном запросе Hibernate сможет вернуть их гораздо быстрее.
Кэш первого уровня
В примере выше только первый вызов get ( ) инициирует запрос к базе, второй вызов будет обслужен уже из кэша и обращения к базе не произойдёт.
Интересно поведение кэша первого уровня при использовании ленивой загрузки. При загрузке объекта методом load ( ) или объекта с лениво загружаемыми полями, лениво загружаемые данные в кэш не попадут. При обращении к данным будет выполнен запрос в базу и данные будут загружены и в объект и в кэш. А вот следующая попытка лениво загрузить объект приведёт к тому, что объект сразу вернут из кэша и уже полностью загруженным.
Кэш второго уровня
Если кэш первого уровня существует только на уровне сессии и persistence context, то кэш второго уровня находится выше — на уровне SessionFactory и, следовательно, один и тот же кэш доступен одновременно в нескольких persistence context. Кэш второго уровня требует некоторой настройки и поэтому не включен по умолчанию. Настройка кэша заключается в конфигурировании реализации кэша и разрешения сущностям быть закэшированными.
Конфигурирование кэша
Hibernate не реализует сам никакого in-memory сache, а использует существующие реализации кэшей. Раньше Hibernate самостоятельно поддерживал интерфейс с этими кэшами, но сейчас существует JCache и корректнее будет использовать этот интерфейс. Реализаций у JCache множество, но я выберу ehcache, как одну из самых распространённых.
В первую очередь надо добавить поддержку JCache и ehcache в зависимости:
Затем настроить hibernate на использование ehcache для кэширования:
name = «hibernate.cache.region.factory_class» > org.hibernate.cache.jcache.JCacheRegionFactory
name = «hibernate.javax.cache.provider» > org.ehcache.jsr107.EhcacheCachingProvider
В первой строке мы говорим Hibernate, что хотим использовать JCache интерфейс, а во второй строке выбираем конкретную реализацию JCache: ehcache.
Наконец, включим кэш второго уровня:
name = «hibernate.cache.use_second_level_cache» > true
@Cacheable и @Cache
@Cache это аннотация Hibernate, настраивающая тонкости кэширования объекта в кэше второго уровня Hibernate. Аннотации @Cacheable достаточно, чтобы объект начал кэшироваться с настройками по умолчанию. При этом @Cache использованная без @Cacheable не разрешит кэширование объекта.
@Cache принимает три параметра:
Последний пункт достаточно объёмен, чтобы рассматривать его внутри списка. Проблема заключается в том, что кэш второго уровня доступен из нескольких сессий сразу и несколько потоков программы могут одновременно в разных транзакциях работать с одним и тем же объектом. Следовательно надо как-то обеспечивать их одинаковым представлением этого объекта.
Стратегий одновременного доступа к объектам в кэше в hibernate существует четыре:
Список выше отсортирован по нарастанию производительности, transactional стратегия самая медленная, read-only самая быстрая. Недостатком read-only стратегии является её бесполезность, в случае если объекты постоянно изменяются, так как в этом случае они не будут задерживаться в кэше.
Использование кэша второго уровня требует изменений в конфигурации Hibernate и в коде сущностей, но не требует изменения кода запросов и управления сущностями:
Русские Блоги
Введение в атрибуты аннотации @Cacheable (незакончено)
Содержание статьи:
1. атрибут value / cacheNames
Как показано на рисунке ниже, эти два атрибута имеют одинаковое значение. @AliasFor Комментарии можно увидеть. Эти два атрибута используются для указания имени компонента кэша, т. Е. В какой кэш помещать результат возврата метода. Атрибут определяется как массив, и можно указать несколько кешей;
2. ключевой атрибут
Можно использовать компиляцию значения ключаSpEL Выражения; кроме того, мы также можем использовать keyGenerator Чтобы указать ключ в пути генератора, нам нужно только написать keyGenerator и зарегистрировать генератор в контейнере IOC. (Использование keyGenerator, продолжайте смотреть вниз)
Имя | должность | описание | пример |
---|---|---|---|
methodName | root object | Имя вызываемого в данный момент метода | #root.method.name |
method | root object | В настоящее время вызываемый метод | #root.methodName |
target | root object | Целевой объект, вызываемый в настоящее время | #root.target |
targetClass | root object | Класс целевого объекта, вызываемый в данный момент | #root.targetClass |
args | root object | Список параметров вызываемого в данный момент метода | #root.args[0] |
caches | root object | Список кешей, используемый текущим вызовом метода (например, @Cacheable (value = <"cache1", "cache2">)), есть два кеша | #root.caches[0].name |
argument name | evaluation context | Имя параметра метода. Вы можете напрямую # имя параметра или использовать форму # p0 или # a0, 0 представляет индекс параметра; | #id、#p0、#a0 |
result | evaluation context | Возвращаемое значение после выполнения метода (только если решение после выполнения метода является допустимым, например выражение «если», «помещено в кеш» и выражение «кешевикт» beforeInvocation = false) | #result |
Примеры использования следующие:
3.keyGenerator свойство
Генератор ключей. Если сложно указать через параметры, мы можем сами указать идентификатор компонента генератора ключей. Атрибут key / keyGenerator: выберите тот, который хотите использовать. Мы можем зарегистрировать keyGenerator в контейнере IOC, чтобы использовать его, настроив класс конфигурации.
4.cacheManager: укажите диспетчер кеша (незавершенный)
(Представлено позже при интеграции кеш-менеджера)
5.cacheResolver: укажите для получения резолвера (незавершенный)
(выберите один из cacheManager / cacheResolver)
6. атрибут условия
Атрибут условия суждения используется, чтобы указать, что кэш можно кэшировать только при соблюдении указанных условий. Также может пройтиSpEL Выражение установлено. Это правило конфигурации совпадает с правилом конфигурации в таблице выше.
7. если только атрибут
Атрибут if означает «если». То есть, только если условие, указанное в if, истинно, возвращаемое значение метода не будет кэшироваться. Об этом можно будет судить после получения результата.
8. атрибут синхронизации
Использование аннотаций @Cacheable, введение заканчивается здесь
Если эта статья была вам полезна, пожалуйста, поставьте мне палец вверх
Собеседование по Java EE — Java Persistence API (JPA) (вопросы и ответы). Часть 2
Вторая часть вопросов и ответов на собеседование по Java EE — Java Persistence API (JPA).
к списку вопросов раздела JEE
Вопросы
30. Для чего нужна аннотация Basic?
31. Для чего нужна аннотация Access?
32. Какими аннотациями можно перекрыть связи (override entity relationship) или атрибуты, унаследованные от суперкласса, или заданные в embeddable классе при использовании этого embeddable класса в одном из entity классов и не перекрывать в остальных?
33. Какой аннотацией можно управлять кешированием JPA для данного Entity?
34. Какие аннотации служат для задания класса преобразования basic атрибута Entity в другой тип при сохранении/получении данных их базы (например, работать с атрибутом Entity boolean типа, но в базу сохранять его как число)?
35. Какой аннотацией можно задать класс, методы которого должны выполниться при определенных JPA операциях над данным Enitity или Mapped Superclass (такие как удаление, изменение данных и т.п.)?
36. Для чего нужны callback методы в JPA? К каким сущностям применяются аннотации callback методов? Перечислите семь callback методов (или, что тоже самое, аннотаций callback методов)
37. Какие аннотации служат для установки порядка выдачи элементов коллекций Entity?
38. Какой аннотацией можно исключить поля и свойства Entity из маппинга (property or field is not persistent)?
40. Какие два вида кэшей (cache) вы знаете в JPA и для чего они нужны?
41. Какие есть варианты настройки second-level cache (кэша второго уровня) в JPA или что аналогично опишите какие значения может принимать элемент shared-cache-mode из persistence.xml?
42. Как можно изменить настройки fetch стратегии любых атрибутов Entity для отдельных запросов (query) или методов поиска (find), то если у Enity есть атрибут с fetchType = LAZY, но для конкретного запроса его требуется сделать EAGER или наоборот?
43. Каким способом можно в коде работать с кэшем второго уровня (удалять все или определенные Entity из кеша, узнать закэшировался ли данное Entity и т.п.)?
44. Каким способом можно получить метаданные JPA (сведения о Entity типах, Embeddable и Managed классах и т.п.)?
45. Что такое JPQL (Java Persistence query language) и чем он отличается от SQL?
46. Что означает полиморфизм (polymorphism) в запросах JPQL (Java Persistence query language) и как его «выключить»?
47. Что такое Criteria API и для чего он используется?
48. В чем разница в требованиях к Entity в Hibernate, от требований к Entity, указанных в спецификации JPA (см. вопрос 10)?
49. Какая уникальная стратегия наследования есть в Hibernate, но нет в спецификации JPA?
50. Какие основные новые возможности появились в спецификации JPA 2.1 по сравнению с JPA 2.0 (перечислите хотя бы пять-шесть новых возможностей)?
Ответы
30. Для чего нужна аннотация Basic?
Basic — указывает на простейший тип маппинга данных на колонку таблицы базы данных. Также в параметрах аннотации можно указать fetch стратегию доступа к полю и является ли это поле обязательным или нет.
31. Для чего нужна аннотация Access?
Она определяет тип доступа (access type) для класса entity, суперкласса, embeddable или отдельных атрибутов, то есть как JPA будет обращаться к атрибутам entity, как к полям класса (FIELD) или как к свойствам класса (PROPERTY), имеющие гетеры (getter) и сетеры (setter).
32. Какими аннотациями можно перекрыть связи (override entity relationship) или атрибуты, унаследованные от суперкласса, или заданные в embeddable классе при использовании этого embeddable класса в одном из entity классов и не перекрывать в остальных?
Для такого перекрывания существует четыре аннотации:
1. AttributeOverride чтобы перекрыть поля, свойства и первичные ключи,
2. AttributeOverrides аналогично можно перекрыть поля, свойства и первичные ключи со множественными значениями,
3. AssociationOverride чтобы перекрывать связи (override entity relationship),
4. AssociationOverrides чтобы перекрывать множественные связи (multiple relationship).
33. Какой аннотацией можно управлять кешированием JPA для данного Entity?
Cacheable — позволяет включить или выключить использование кеша второго уровня (second-level cache) для данного Entity (если провайдер JPA поддерживает работу с кешированием и настройки кеша (second-level cache) стоят как ENABLE_SELECTIVE или DISABLE_SELECTIVE, см вопрос 41). Обратите внимание свойство наследуется и если не будет перекрыто у наследников, то кеширование изменится и для них тоже.
34. Какие аннотации служат для задания класса преобразования basic атрибута Entity в другой тип при сохранении/получении данных их базы (например, работать с атрибутом Entity boolean типа, но в базу сохранять его как число)?
Convert и Converts — позволяют указать класс для конвертации Basic атрибута Entity в другой тип (Converts — позволяют указать несколько классов конвертации). Классы для конвертации должны реализовать интерфейс AttributeConverter и могут быть отмечены (но это не обязательно) аннотацией Converter.
35. Какой аннотацией можно задать класс, методы которого должны выполниться при определенных JPA операциях над данным Enitity или Mapped Superclass (такие как удаление, изменение данных и т.п.)?
Аннотация EntityListeners позволяет задать класс Listener, который будет содержать методы обработки событий (сallback methods) определенных Entity или Mapped Superclass.
36. Для чего нужны callback методы в JPA? К каким сущностям применяются аннотации callback методов? Перечислите семь callback методов (или, что тоже самое, аннотаций callback методов)
Callback методы служат для вызова при определенных событиях Entity (то есть добавить обработку например удаления Entity методами JPA), могут быть добавлены к entity классу, к mapped superclass, или к callback listener классу, заданному аннотацией EntityListeners (см предыдущий вопрос). Существует семь callback методов (и аннотаций с теми же именами):
1) PrePersist
2) PostPersist
3) PreRemove
4) PostRemove
5) PreUpdate
6) PostUpdate
7) PostLoad
37. Какие аннотации служат для установки порядка выдачи элементов коллекций Entity?
Для этого служит аннотация OrderBy и OrderColumn.
38. Какой аннотацией можно исключить поля и свойства Entity из маппинга (property or field is not persistent)?
Для этого служит аннотация Transient.
39. Какие шесть видов блокировок (lock) описаны в спецификации JPA (или какие есть значения у enum LockModeType в JPA)?
У JPA есть шесть видов блокировок, перечислим их в порядке увеличения надежности (от самого ненадежного и быстрого, до самого надежного и медленного):
1) NONE — без блокировки
2) OPTIMISTIC (или синоним READ, оставшийся от JPA 1) — оптимистическая блокировка,
3) OPTIMISTIC_FORCE_INCREMENT (или синоним WRITE, оставшийся от JPA 1) — оптимистическая блокировка с принудительным увеличением поля версионности,
4) PESSIMISTIC_READ — пессимистичная блокировка на чтение,
5) PESSIMISTIC_WRITE — пессимистичная блокировка на запись (и чтение),
6) PESSIMISTIC_FORCE_INCREMENT — пессимистичная блокировка на запись (и чтение) с принудительным увеличением поля версионности.
40. Какие два вида кэшей (cache) вы знаете в JPA и для чего они нужны?
JPA говорит о двух видов кэшей (cache):
1) first-level cache (кэш первого уровня) — кэширует данные одной транзакции,
2) second-level cache (кэш второго уровня) — кэширует данные дольше чем одна транзакция. Провайдер JPA может, но не обязан реализовывать работу с кэшем второго уровня. Такой вид кэша позволяет сэкономить время доступа и улучшить производительность, однако оборотной стороной является возможность получить устаревшие данные.
41. Какие есть варианты настройки second-level cache (кэша второго уровня) в JPA или что аналогично опишите какие значения может принимать элемент shared-cache-mode из persistence.xml?
JPA говорит о пяти значениях shared-cache-mode из persistence.xml, который определяет как будет использоваться second-level cache:
1) ALL — все Entity могут кэшироваться в кеше второго уровня,
2) NONE — кеширование отключено для всех Entity,
3) ENABLE_SELECTIVE — кэширование работает только для тех Entity, у которых установлена аннотация Cacheable(true) или её xml эквивалент, для всех остальных кэширование отключено,
4) DISABLE_SELECTIVE — кэширование работает для всех Entity, за исключением тех у которых установлена аннотация Cacheable(false) или её xml эквивалент
5) UNSPECIFIED — кеширование не определенно, каждый провайдер JPA использует свою значение по умолчанию для кэширования,
42. Как можно изменить настройки fetch стратегии любых атрибутов Entity для отдельных запросов (query) или методов поиска (find), то если у Enity есть атрибут с fetchType = LAZY, но для конкретного запроса его требуется сделать EAGER или наоборот?
Для этого существует EntityGraph API, используется он так: с помощью аннотации NamedEntityGraph для Entity, создаются именованные EntityGraph объекты, которые содержат список атрибутов у которых нужно поменять fetchType на EAGER, а потом данное имя указывается в hits запросов или метода find. В результате fetchType атрибутов Entity меняется, но только для этого запроса. Существует две стандартных property для указания EntityGraph в hit:
1) javax.persistence.fetchgraph — все атрибуты перечисленные в EntityGraph меняют fetchType на EAGER, все остальные на LAZY
2) javax.persistence.loadgraph — все атрибуты перечисленные в EntityGraph меняют fetchType на EAGER, все остальные сохраняют свой fetchType (то есть если у атрибута, не указанного в EntityGraph, fetchType был EAGER, то он и останется EAGER)С помощью NamedSubgraph можно также изменить fetchType вложенных объектов Entity.
43. Каким способом можно в коде работать с кэшем второго уровня (удалять все или определенные Entity из кеша, узнать закэшировался ли данное Entity и т.п.)?
Для работы с кэшем второго уровня (second level cache) в JPA описан Cache интерфейс, содержащий большое количество методов по управлению кэшем второго уровня (second level cache), если он поддерживается провайдером JPA, конечно. Объект данного интерфейса можно получить с помощью метода getCache у EntityManagerFactory.
44. Каким способом можно получить метаданные JPA (сведения о Entity типах, Embeddable и Managed классах и т.п.)?
Для получения такой информации в JPA используется интерфейс Metamodel. Объект этого интерфейса можно получить методом getMetamodel у EntityManagerFactory или EntityManager.
45. Что такое JPQL (Java Persistence query language) и чем он отличается от SQL?
JPQL (Java Persistence query language) это язык запросов, практически такой же как SQL, однако вместо имен и колонок таблиц базы данных, он использует имена классов Entity и их атрибуты. В качестве параметров запросов так же используются типы данных атрибутов Entity, а не полей баз данных. В отличии от SQL в JPQL есть автоматический полиморфизм (см. следующий вопрос). Также в JPQL используется функции которых нет в SQL: такие как KEY (ключ Map’ы), VALUE (значение Map’ы), TREAT (для приведение суперкласса к его объекту-наследнику, downcasting), ENTRY и т.п.
46. Что означает полиморфизм (polymorphism) в запросах JPQL (Java Persistence query language) и как его «выключить»?
В отличии от SQL в запросах JPQL есть автоматический полиморфизм, то есть каждый запрос к Entity возвращает не только объекты этого Entity, но так же объекты всех его классов-потомков, независимо от стратегии наследования (например, запрос select * from Animal, вернет не только объекты Animal, но и объекты классов Cat и Dog, которые унаследованы от Animal). Чтобы исключить такое поведение используется функция TYPE в where условии (например select * from Animal a where TYPE(a) IN (Animal, Cat) уже не вернет объекты класса Dog).
47. Что такое Criteria API и для чего он используется?
Criteria API это тоже язык запросов, аналогичным JPQL (Java Persistence query language), однако запросы основаны на методах и объектах, то есть запросы выглядят так:
Spring Cache: от подключения кэширования за 1 минуту до гибкой настройки кэш-менеджера
Раньше я боялся кэширования. Очень не хотелось лезть и выяснять, что это такое, сразу представлялись какие-то подкапотные люто-энтерпрайзные штуки, в которых может разобраться только победитель олимпиады по математике. Оказалось, что это не так. Кэширование оказалось очень простым, понятным и невероятно лёгким во внедрении в любой проект.
В данном посте я постараюсь объяснить о кэшировании так же просто, как это сейчас понимаю я. Вы узнаете о том, как внедрить кэширование за 1 минуту, как кэшировать по ключу, устанавливать время жизни кэша, и многие прочие штуки, которые необходимо знать, если Вам поручили закэшировать что-то в вашем рабочем проекте, и Вы не хотите ударить в грязь лицом.
Почему я говорю «поручили»? Потому что кэширование, как правило, есть смысл применять в больших, высоконагруженных проектах, с десятками тысяч запросов в минуту. В таких проектах, чтобы не перегружать базу, как правило, кэшируют обращения к репозиторию. Особенно если известно, что данные из какой-нибудь мастер-системы обновляются с некоторой периодичностью. Сами мы такие проекты не пишем, мы на них работаем. Если же проект маленький и перегрузки ему не грозят, тогда, конечно, лучше ничего не кэшировать — всегда свежие данные всегда лучше периодически обновляемых.
Обычно в обучающих постах докладчик сначала лезет под капот, начинает копаться в кишочках технологии, чем немало утомляет читателя, а уж потом, когда тот пролистал без дела добрую половину статьи и ничего не понял, повествует, как это работает. У нас всё будет иначе. Сначала мы делаем так, чтобы заработало, и желательно, с приложением наименьших усилий, а уж потом, если интересно, Вы сможете заглянуть под капот кэширования, посмотреть изнутри сам бин и тонко настроить кэширование. Но даже если Вы этого не сделаете (а это начинается с 6 пункта), Ваше кэширование будет работать и так.
Мы создадим проект, в котором разберём все те аспекты кэширования, которые я обещал. В конце, как обычно, будет ссылка на сам проект.
0. Создание проекта
Мы создадим очень простой проект, в котором мы сможем брать сущность из базы данных. Я добавил в проект Lombok, Spring Cache, Spring Data JPA и H2. Хотя, вполне можно обойтись только Spring Cache.
У нас будет только одна сущность, назовём её User.
Добавим репозиторий и сервис:
Когда мы заходим в сервисный метод get(), мы пишем об этом в лог.
Подключим к проекту Spring Cache.
1. Кэширование возвращаемого результата
Что делает Spring Cache? Spring Cache просто кэширует возвращаемый результат для определённых входных параметров. Давайте это проверим. Мы поставим аннотацию @Cacheable над сервисным методом get(), чтобы кэшировать возвращаемые данные. Дадим этой аннотации название «users» (далее мы разберём, зачем это делается, отдельно).
Для того, чтобы проверить, как это работает, напишем простой тест.
Если над классом стоит своя аннотация @SpringBootTest, для такого класса каждый раз заново поднимается контекст. Поскольку контекст может подниматься 5 секунд, а может 40 секунд, это в любом случае очень сильно тормозит процесс тестирования. При этом, разницы в контексте обычно нет никакой, и при запуске каждой группы тестов в пределах одного класса нет необходимости заново запускать контекст. Если же мы ставим только одну аннотацию, скажем, над абстрактным классом, как в нашем случае, это позволяет поднимать контекст только один раз.
Поэтому я предпочитаю сокращать количество поднимаемых контекстов при тестировании/сборке, если это возможно.
Что делает наш тест? Он создаёт двоих юзеров и потом по 2 раза вытаскивает их из базы. Как мы помним, мы поместили аннотацию @Cacheable, которая будет кэшировать возвращаемые значения. После получения объекта из метода get() мы выводим объект в лог. Также, мы выводим в лог информацию о каждом посещении приложением метода get().
Запустим тест. Вот что мы получаем в консоль.
Как мы видим, первые два раза мы действительно сходили в метод get() и реально получили юзера из базы. Во всех остальных случаях, реального захода в метод не было, приложение брало закэшированные данные по ключу (в данном случае, это id).
2. Объявление ключа для кэширования
Бывают ситуации, когда в кэшируемый метод приходит несколько параметров. В таком случае, бывает нужно определить параметр, по которому будет происходить кэширование. Добавим в пример метод, который будет сохранять в базу сущность, собранную по параметрам, но если сущность с таким именем уже есть, мы не будем её сохранять. Для этого мы определим параметр name как ключ для кэширования. Выглядеть это будет так:
Напишем соответствующий тест:
Мы попытаемся создать троих пользователей, для двоих из которых будет совпадать имя
и для двоих из которых будет совпадать email
В методе создания мы логируем каждый факт обращения к методу, а также, мы будем логировать все сущности, которые этот метод нам вернул. Результат будет таким:
Мы видим, что фактически приложение вызывало метод 3 раза, а заходило в него только два раза. Один раз для метода совпадал ключ, и он просто возвращал закэшированное значение.
3. Принудительное кэширование. @CachePut
Бывают ситуации, когда мы хотим кэшировать возвращаемое значение для какой-то сущности, но в то же время, нам нужно обновить кэш. Для таких нужд существует аннотация @CachePut. Оно пропускает приложение в метод, при этом, обновляя кэш для возвращаемого значения, даже если оно уже закэшировано.
Добавим пару методов, в которых мы будем сохранять юзера. Один из них мы пометим обычной аннотацией @Cacheable, второй — @CachePut.
Первый метод будет просто возвращать закэшированные значения, второй — принудительно обновлять кэш. Кэширование будет осуществляться по ключу #user.name. Напишем соответствующий тест.
По той логике, которая уже описывалась, при первом сохранении пользователя с именем «Vasya» через метод createOrReturnCached() далее мы будем получать кэшированную сущность, при этом, в сам метод приложение заходить не будет. Если же мы вызовем метод createAndRefreshCache(), кэшированная сущность для ключа с именем «Vasya» перезапишется в кэше. Выполним тест и посмотрим, что будет выведено в консоль.
Мы видим, что user1 благополучно записался в базу и кэш. При повторной попытке записать юзера с таким же именем мы получаем закэшированный результат выполнения первого обращения (user2, для которого id такой же, как у user1, что говорит нам о том, что юзер не был записан, и это просто кэш). Далее, мы пишем третьего пользователя через второй метод, который даже при имеющемся закэшированном результате всё равно вызвал метод и записал в кэш новый результат. Это user3. Как мы видим, у него уже новый id. После чего, мы вызываем первый метод, который берёт новый кэш, добавленный user3.
4. Удаление из кэша. @CacheEvict
Иногда возникает необходимость жёстко обновить какие-то данные в кэше. Например, сущность уже удалена из базы, но она по-прежнему доступна из кэша. Для сохранения консистентности данных, нам необходимо хотя бы не хранить в кэше удалённые данные.
Добавим в сервис ещё пару методов.
Первый будет просто удалять пользователя, второй тоже будет его удалять, но мы пометим его аннотацией @CacheEvict. Добавим тест, который будет создавать двух юзеров, после чего, одного будет удалять через простой метод, а второго — через аннотируемый метод. После чего, мы достанем этих юзеров через метод get().
Логично, что раз наш юзер уже закэширован, удаление не помешает нам его как бы получить — ведь он закэширован. Посмотрим логи.
Мы видим, что приложение благополучно сходило оба раза в метод get() и Spring закэшировал эти сущности. Далее, мы удалили их через разные методы. Первый мы удалили обычным путём, и закэшированное значение осталось, поэтому когда мы попытались получить юзера под id 1, нам это удалось. Когда же мы попытались получить юзера 2, метод вернул нам EntityNotFoundException — такого юзера в кэше не оказалось.
5. Группировка настроек. @Caching
Иногда один метод требует нескольких настроек кэширования. Для этих целей используется аннотация @Caching. Выглядеть это может приблизительно так:
Это единственный способ группировать аннотации. Если Вы попытаетесь нагородить что-то вроде
то IDEA сообщит Вам, что так нельзя.
6. Гибкая настройка. CacheManager
Наконец-то мы разобрались с кэшем, и он перестал быть для нас чем-то непонятным и страшным. Теперь давайте заглянем под капот и посмотрим, как мы можем настроить кэширование в целом.
Для таких задач существует CacheManager. Он существует везде, где есть Spring Cache. Когда мы добавили аннотацию @EnableCache, такой кэш менеджер автоматически будет создан Spring. Мы можем убедиться в этом, если заавтовайрим ApplicationContext и вскроем его на брейкпоинте. Среди прочих бинов, будет и бин «cacheManager».
Я остановил приложение на этапе, когда уже два юзера были созданы и помещены в кэш. Если мы вызовем нужный нам бин через Evaluate Expression, то мы увидим, что такой бин действительно есть, в нём есть ConcurentMapCache с ключом «users» и значением ConcurrentHashMap, в которой уже лежат закэшированные юзеры.
Мы, в свою очередь, можем создать свой кэш-менеджер, с хабром и программистами, после чего, тонко настроить его на наш вкус.
Осталось только выбрать, какой именно кэш-менеджер мы будем использовать, потому что их предостаточно. Я не буду перечислять все кэш-менеджеры, достаточно будет знать, что есть такие:
Итак, досоздадим наш кэш-менеджер.
Наш кэш-менеджер готов.
7. Настройка кэша. Время жизни, максимальный размер и проч.
Для этого нам потребуется довольно популярная библиотека Google Guava. Я взял последнюю.
При создании кэш-менеджера переопределим метод createConcurrentMapCache, в котором вызовем CacheBuilder от Guava. В процессе нам будет предложено настроить кэш-менеджер при помощи инициализации следующих методов:
Определим в менеджере время жизни записи. Чтобы долго не ждать, выставим 1 секунду.
Напишем соответствующий такому случаю тест.
Мы сохраняем несколько значений в базу данных, причём, если данные закэшированы, мы ничего не сохраняем. Сначала мы сохраняем два значения, потом ожидаем 1 секунду, пока кэш не протухнет, после чего, сохраняем ещё одно значение.
Логи показывают, что сначала мы создали юзера, потом попытались ещё одного, но поскольку данные были закэшированы, мы получили их из кэша (в обоих случаях — при сохранении и при получении из базы). Потом протух кэш, о чём сообщает нам запись о фактическом сохранении и фактическом получении юзера.
8. Подведу итог
Рано или поздно, разработчик сталкивается с необходимостью реализации кэширования в проекте. Я надеюсь, что эта статья поможет Вам разобраться в предмете и смотреть на вопросы кэширования смелее.