Кешування другого рівня в Hibernate.

Частина II: Бібліотеки кешування для Hibernate

Кеш другого рівня в Hibernate може бути підключеним і може бути в межах процесу або кластера. Можуть бути різні реалізації кешу другого рівня, існують кілька вже готових реалізацій на основі готових движків кешування. Але можна реалізувати власний движок і підключити його за допомогою реалізації інтерфейсу org.hibernate.cache.CacheProvider.

Визначають наступні провайдери кешу другого рівня

- EHCache. Підтримка кешування другого рівня в межах одного процесу однієї віртуальної машини. Може виконувати зберігання кешу як в оперативній памяті, так і на диск. Підтримує кешування запитів.

- OSCache (OpenSymphony Cache). Підтримка кешування другого рівня в межах одного процесу однієї віртуальної машини. Підтримує кешування запитів.

- SwarmCache. Підтримка кешування другого рівня в межах кластера. Не підтримує кешування запитів.

- JBossCache. Підтримка кешування другого рівня в межах кластера. Підтримує кешування запитів.

Про підтримку стратегій кешування різними провайдерами можна дізнатися із наступної таблиці:

Провайдер

Read-only

Read-write

Nonstrict-read-write

Transactional

EHCache

+

+

+

OSCache

+

+

+

SwarmCache

+

+

JBossCache

+

+

Підтримка кешування другого рівня в Hibernate

Кешування другого рівня в Hibernate потрібно увімкнути, для того, щоб його використовувати. Увімкнути кешування другого рівня можна встановши property в конфігурації сесії Hibernate:

<property name="cache.use_second_level_cache">true</property>

Якщо необхідно увімкнути також кешування для запитів, то слід також встановити значення для property в конфігурації сесії Hibernate:

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

Також необхідно вказати клас провайдера кешування, а для цього треба задати наступний property в конфігурації сесії Hibernate :

- для провайдера EHCache

<property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

- для провайдера OSCache

<property name="cache.provider_class">org.hibernate.cache.OSCacheProvider</property>

- для провайдера SwarmCache

<property name="cache.provider_class">org.hibernate.cache.SwarmCacheProvider</property>

Але для того, щоб кешування другого рівня було в дії, слід позначити хоча б один клас для кешування. Позначити певний клас для кешування можна двома способами:

  1. В описі маппінгу класу у файлі *.hbm.xml добавити елемент cache із відповідним значенням.

<class name=”org.prisoft.sample.hibernate.Country” table=”Country” mutable="false">

<cache usage="read-only"/>

</class>

Вище описаний клас Country, який мапиться на таблицю Country. Ця таблиця є словником країн. Словник зміні не підлягає, тобто значення не добавляється, не редагується та не видаляється. <cache> вказує на те, що дані підлягають кешуванню, стратегія кешування read-only.

  1. У файлі конфігурації сесії hibernate.cfg.xml. Для цього використовується елемент <class-cache>, який наводиться після списку ресурсів. Ось як може описуватися кешування для класу Country:

<hibernate-configuration>

<session-factory>

….

<mapping resource=”org/prisoft/sample/hibernate/Country.hbm.xml”/>

….

<class-cache class=”org.prisoft.sample.hibernate.Country”

usage=”read-only”/>

</session-factory>

</hibernate-configuration>

Я надаю перевагу конфігурації кешування другого рівня через файл hibernate.cfg.xml, оскільки так видно повний список класів, що підлягають кешуванню, а також при необхідності, можна легко відключити кешування.

Отже, ми розглянули, як включити кешування другого рівня, а також як вказати які саме ентіті підлягають кешуванню. Ми не розглянули, як задається кешування для запитів.

Також ми не розглянули таке поняття, як регіони. Регіони – це так би мовити іменовані екземпляри кешу, в якому знаходяться різні класи із різними політиками часу на існування. По замовчуванню, ім’я регіону складає повна назва класу (якщо це кеш класу) або повна назва класу плюс імя property класу (якщо це колекція). За допомогою property сесії Hibernate hibernate.cache.region_prefix. Тоді ім’я регіону буде складатися із префіксу та повного імені класу. Вказати ім’я регіону можна явно за допомогою атрибута region в елементі <cache> або <class-cache>.

EHCache (http://ehcache.sourceforge.net)

Останнє обновлення аплікації це є ehcache-1.4-beta, яка вийшла 1 січня 2008. А це означає, що продукт не тільки живий, але і розвивається. Розповсюджується EhCache за ліцензією The Apache Software License, Version 2.0. Спонсори там також далеко не з послідніх. Так що, проект живе і розвивається. Не може не радувати повна підтримка JSR107 (JCache API).

Серед вказаних характеристик слід відзначити:

- Простота (API є досить простим, початкова конфігурація також не є необхідною).

- Залежність тільки від commons-logging та commons-collections. Вони в свою чергу також поширюються за APL. Та й більшість проектів їх використовує. То ж, ця залежність не є проблематичною.

- Кешування в оперативній пам’яті та на диск. Підтримка великих розмірів.

- Підтримка великої кількості кешів.

- Підтримка розширень для кешу (а також розширення для загружчиків та обробників виключень).

- А також багато чого іншого, про що ви можете точніше дізнатися за адресом http://ehcache.sourceforge.net/features.html

Як уже зазначено, можна і не конфігурувати EHCache. Але я не думаю, що це хороша ідея. Нам вона явно не підходить. Ми повинні навчитися конфігурувати EhCache.

Отже, конфігурація EHCache знаходиться у файлі ehcache.xml, який у свою чергу знаходиться в класспасі аплікації.

Отже, структура конфігураційного файла:

<ehcache>

<diskStore …/>

<defaultCache …/>

<cache name=”...” …/>

</ehcache>

Конфігураційний файл може містити не тільки ці елементи, але й інші. Але вони розглядатися не будуть.

Роглянемо детальніше ці елементи.

<diskStore path=”java.io.tmpdir”/> - вказує шлях до каталогу, де файли кешу створюються.

Наступні значення є можливими:

- user.homeдомашній каталог користувача;

- user.dirтекучий робочий каталог користувача;

- java.io.tmpdir – каталог тимчасових файлів.

Можна також вказувати субдиректорії, наприклад <diskStore path=”java.io.tmpdir/cache/myapp”/>

Конфігурація кешу, який не описаний за допомогою власного <cache> елемента, відбувається через <defaultCache>.

<defaultCache

maxElementsInMemory=”10000”

eternal=”false”

timeToIdleSeconds=”120”

timeToLiveSeconds=”120”

overflowToDisk=”true”

diskSpoolBufferSizeMB=”30”

maxElementsOnDisk=”1000000”

diskPersistent=”false”

diskExpiryThreadInternalSeconds=”120”

memoryStoreEvictionPolicy=”LRU”/>

Можна також задавати конфігурацію для окремого кешу; а для цього використовується наступний елемент

<сache name=”CacheName”

maxElementsInMemory=”10000”

eternal=”false”

timeToIdleSeconds=”120”

timeToLiveSeconds=”120”

overflowToDisk=”true”

maxElementsOnDisk=”1000000”

diskPersistent=”false”

diskExpiryThreadInternalSeconds=”120”

memoryStoreEvictionPolicy=”LRU”/>

А тепер детальніше про кожен атрибут.

mameназва кешу; назва, як ви вже, мабуть, зрозуміли має бути унікальна.

maxElementsInMemory – максимальна кількість об’єктів, які будуть створені та зберігатися в кеші в оперативній пам’яті.

maxElementsOnDisk – максимальна кількість об’єктів, які будуть збережені на диск.

eternalвказує на те, чи є елементи вічними. Якщо вони є вічними, то будь-які настройки Idle та Live Seconds ігноруються, елементи зберігаються до кінця.

timeToIdleSeconds час життя невикористаного елемента кешу (за умови, якщо eternal рівний false). Якщо значення 0, то це значить, що елемент може залишатися в кеші в стані не використовуваного нескінченний період.

timeToLiveSeconds – час життя елемента кешу (за умови, якщо eternal рівний false). Якщо значення 0, то це значить, що елемент може залишатися в кеші в стані невикористаного нескінченний період.

overflowToDisk – вказує на те, чи зберігати елементи на диск, якщо досягнуто максимуму елементів в пам’яті.

diskPersistent - чи зберігаються закешовані дані між пере запуском віртуальної машини. Значення по-замовчуванню false.

diskExpiryThreadInternalSeconds – кількість секунд, через які відбувається запуск потоку очищення диску від елементів кешу, що завершили свій час життя.

memoryStoreEvectionPolicy. Розрізняються наступні політики LRULeast Recently Used, FIFO – First In First Out, LFU Last Frequently Used.

Приклад аплікації із використанням кешу EHCache

Отже, розглянуто трохи теорії, а тепер ближче до практики. Для цього було створено просту аплікацію, яка містить 4 доменних об’єкта: Country, Materials, Product, ProductMaterial.

Отже, ми маємо аплікацію, яка містить інформацію про матеріали (назва, код, ціна, країна виробник), про продукцію (назва, код, ціна, країна виробник, матеріали із яких виготовлена продукція) і власне словник країн виробників.

Кешування класів та стратегії кешування описані у файлі hibernate.cfg.xml.

Отже, hibernate.cfg.xml має вигляд:

<hibernate-configuration>

<session-factory>

<property name="hibernate.connection.url">jdbc:mysql://localhost/proregister</property>

<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

<property name="hibernate.connection.username">root</property>

<property name="hibernate.connection.password"/>

<property name="hibernate.show_sql">false</property>

<mapping resource="org/prisoft/sample/proregister/objects/Country.hbm.xml"/>

<mapping resource="org/prisoft/sample/proregister/objects/Material.hbm.xml"/>

<mapping resource="org/prisoft/sample/proregister/objects/Product.hbm.xml"/>

<mapping resource="org/prisoft/sample/proregister/objects/ProductMaterial.hbm.xml"/>

<class-cache class="org.prisoft.sample.proregister.objects.Country" usage="read-only"/>

<class-cache class="org.prisoft.sample.proregister.objects.Material" usage="read-write"/>

<class-cache class="org.prisoft.sample.proregister.objects.Product" usage="read-write"/>

<class-cache class="org.prisoft.sample.proregister.objects.ProductMaterial" usage="read-write"/>

</session-factory>

</hibernate-configuration>

Клас Country має стратегію кешування read-only, в той час як всім іншим задана стратегія read-write. Country є по факту незмінним словником країн; його не можна змінювати.

Кеши класів Material, Product та ProductMaterial повинні підтримувати зміну, оскільки елементи можуть як добавлятися, так і редагуватися та видалятися. Саме тому їм задана стратегія read-write.

Аплікація працюватиме із базою даних на одній машині, тому виконувати кешування на диск немає потреби. Окрім того, кількість даних є невеликою, тому їх можна спокійно зберігати в базу даних.

Для детальної конфігурації EHCache потрібно конфігурувати кожен кеш у файлі ehcache.xml.

Отже, конфігурація має наступний вигляд:

<ehcache>

<diskStore path="java.io.tmpdir"/>

<defaultCache

maxElementsInMemory="10000"

eternal="false"

timeToIdleSeconds="120"

timeToLiveSeconds="120"

overflowToDisk="false"

diskPersistent="false"

diskExpiryThreadIntervalSeconds="120"

memoryStoreEvictionPolicy="LRU"/>

[тут має бути конфігурація кешів, розглянута дальше]

</ehcache>

Отже, ми вказали на каталог для файлів кешування та конфігурацію кешу по-замовчуванню.

Нижче подана конфігурація кешу для класу Country. Задаємо максимальну кількість елементів в пам’яті, забороняється записувати дані на диск, дані можуть зберігатися тривалий час. Якщо кількість елементів в пам’яті перевищує 200, то тоді відбувається очистка кешу.

<cache name="org.prisoft.sample.proregister.objects.Country "

maxElementsInMemory="200"

eternal="true"

overflowToDisk="false"/>

Настройка кешу для об’єктів Product, Material та ProductMaterial має відмінності, оскільки ці об’єкти можуть змінюватися, тому для них задана конфігурація тривалості існування елементів кешу. Конфігурація подана нижче.

<cache name=”org.prisoft.sample.proregister.Product”

maxElemenentsInMemory=”2000”

eternal=”false”

overflowToDisk=”false”

timeToIdleSeconds=”120”

timeToLiveSeconds=”300”/>

<cache name=”org.prisoft.sample.proregister.Material”

maxElemenentsInMemory=”5000”

eternal=”false”

overflowToDisk=”false”

timeToIdleSeconds=”240”

timeToLiveSeconds=”300”/>

<cache name=”org.prisoft.sample.proregister.ProductMaterial”

maxElemenentsInMemory=”10000”

eternal=”false”

overflowToDisk=”false”

timeToIdleSeconds=”120”

timeToLiveSeconds=”120”/>

Як бачите, для різних кешів задана різна кількість елементів в памяті. Атрибут eternal має значення false, тому конфігурація періоду існування елементів у кеші не буде ігноруватися. Дані не зберігаються на диск для жодного із кешів, використовуваних в аплікації.

Отже, ми розглянули які існують провайдери кешу другого рівня для Hibernate, також ми розглянули, як настроїти Hibernate для використання кешу другого рівня для класів (проте не зачепили теми кешування запитів та колекцій). Розглянуто було також більш детально провайдер EHCache.

Додаткова література

1. Hibernate In Action, Manning Publications, 2005

2. EhCache User Guide at http://ehcache.sourceforge.net/EhcacheUserGuide.html

Кешування другого рівня в Hibernate.

Частина I: Трохи теорії

Є багато способів пришвидшити роботу Hibernate. Одним із них є кешування другого рівня (second level cache).

Для мене він знадобився при оптимізації роботи проекту, який використовує Hibernate 3.2.3. В межах аплікації використовується кілька наборів даних, які рідко змінюються. Декотрі дані взагалі не змінюються. Витягувати їх із бази даних кожен раз виходить доволі накладно і навіть довго. Після використання кешування, на звичайній машині різниця почала відмічатися одразу ж.

Взагалі про розгляді кешування в Hibernate, слід розрізняти кешування першого та другого рівнів. Кешування першого рівня є обовязковим, його не можна відключити. Це є кешування рівня одиниці виконання роботи (unit of work) і виконується воно через Session.

Трохи детальніше про кешування першого рівня, адже воно є необхідним для розуміння кешування другого рівня. Перш за все, слід зрозуміти, що це є кешування перстистетних об'єктів, тобто, в межах однієї Session, кешуються об’єкти класу, а не окремі дані, і отримуються для одного і того самого же рядка в базі даних один і той самий об'єкт. Що ж це дає, і чому зрозблено саме так? По-перше, це дозволяє виконувати ідентифікацію по об’єкту, а не по значенню в межах однієї сесії. По-друге, це дозволяє зберігати інформацію про зміни до рядку в межах одного об’єкта, що уже спасає від будь-яких подальших проблем пов’язаних із синхронізацією із базою даних. Отже, кешування першого рівня, використовується в межах сесії одного користувача, і не впливає на роботу інших користувачів. Це пришвидшує роботу.

Кешування другого рівня має зовсім інші межі використання. Навідміну від кешування першого рівня (яке використовується в межах запиту користувача або бізнес транзакції користувача), кешування другого рівня використовується в межах процесу або кластера виконання аплікації. Цей кеш є спільним для багатьох користувачів системи, відповідно, його потрібно осторожно використовувати, та володіти основними правилами.

Перш за все, слід сказати, що кеш другого рівня в Hibernate може мати межі процесу або кластера. Всі сесії використовують спільний кеш. Фактично кеш другого рівня має межі SessionFactory, в той час як кеш першого рівня має межі Session.

Для того, щоб робота кешу другого рівня була задовільною в багатокористувацькому середовищі, слід розуміти стратегії кешування. Стратегія кешування для об’єкта кешування встановлюється незалежно.

Так виділяють наступні стратегії кешування:

- read-only – для об’єктів, що не змінюються ніколи в межах роботи аплікації.

Типове для використання у випадку, якщо значення об’єктів не змінюється в процесі роботи аплікації (наприклад, ролі користувачів, список країн, мов, інші специфічні словники). Для подібних об’єктів можна також використовувати mutable = “false”, що дозволяє Hibernate пришвидшувати роботу для незмінюваних об’єктів.

- read-write – для об’єктів, що можуть змінюватися в межах роботи аплікації, але частота зміни не є великою.

Підходить для використання у більшості випадків. Велика кількість об’єктів в системі змінюється, але при цьому кешування об’єктів є необхідною для покращення роботи.

- nonstrict read-write – для об’єктів, що можуть змінюються в процесі роботи аплікації дуже рідко.

Кеш другого рівня дозволяє зберігати дані як в оперативній пам’яті так і на жорсткому диску. Зберігання даних на жорсткому диску актуально у випадках, коли аплікація та база даних знаходяться на різних машинах.

Коли відбувається загрузка об’єкта за допомогою Session, перш за все, Session виконує перевірку кешу на рахунок наявності в ньому об’єкту із відповідною значенням поля ідентифікації. Перевірка виконується у випадку, якщо для даного типу включено кешування другого рівня. Отже, якщо кеш містить закешований об’єкт відповідним значенням поля ідентифікації, то відбувається створення ново об’єкта у віртувальній машині та заповнення його даними.

Звідси можна зробити наступний висновок. В кеші зберігаються не об’єкти віртуальної машини, а прості дані. На основі цих даних, відбувається заповнення новоствореного об’єкту.

Якщо ж кеш не містить шуканий об’єкт, то відбувається запит до бази даних, і якщо рядок із таким ідентифікатором все-таки є в базі даних, то відбувається його поміщення в кеш. Тепер кеш містить запис для рядка із даним ідентифікатором.