Глава 20. Повышение производительности
Оглавление-
20.1. Стратегии выборки
- 20.1.1. Работа с ленивыми ассоциациями
- 20.1.2. Настройка стратегий выборки
- 20.1.3. Одиносторонняя ассоциация прокси
- 20.1.4. Инициализация коллекций и прокси
- 20.1.5. Использование пакетной выборки
- 20.1.6. Использование выборки подзапросов
- 20.1.7. Выборка профилей
- 20.1.8. Использование ленивой выборки свойств
-
20.2. Кэш второго уровня
- 20.2.1. Отображения кэшей
- 20.2.2. Стратегия: только для чтения
- 20.2.3. Стратегия: чтение/запись
- 20.2.4. Стратегия: нестрогое чтение/запись
- 20.2.5. Стратегия: транзакционная
- 20.2.6. Совместимость с поставщиком кэша/параллельной стратегией
- 20.3. Управление кэшами
-
20.4. Кэш запросов
- 20.4.1. Включение кэширования запросов
- 20.4.2. Области кэша запросов
- 20.5. Улучшение байткода
- 20.6. Понимание производительности коллекций
-
20.7. Мониторинг производительности
- 20.7.1. Мониторинг SessionFactory
- 20.7.2. Метрики
20.1. Стратегии выборки
Hibernate использует стратегию выборки для извлечения ассоциированных объектов, если приложение должно
перемещаться по ассоциации. Стратегии выборки могут быть объявлены в отображении O/R метаданных
или переопределены определенным запросом HQL или Criteria
.
Hibernate определяет следующие стратегии выборки:
-
Join fetching (связывающая выборка): Hibernate получает связанный экземпляр или коллекцию
в том же
SELECT
, используяOUTER JOIN
. -
Select fetching (выборка запросом): второй
SELECT
используется для извлечения связанной сущности или коллекции. Если вы явно не отключите ленивую выборку, указавlazy=«false»
, этот второй select будет выполнен только при доступе к ассоциации. -
Subselect fetching (выборка подзапросом): второй
SELECT
используется для извлечения связанных коллекций для всех сущностей, полученных в предыдущем запросе или выборке. Если вы явно не отключите ленивую выборку, указавlazy=«false»
, этот второй select будет выполнен только при доступе к ассоциации. -
Batch fetching (пакетная выборка): стратегия оптимизации для Select fetching (выборки запросом).
Hibernate получает пакет экземпляров сущностей или коллекций в одном
SELECT
, указывая список первичных или внешних ключей.
Hibernate также различает:
- Immediate fetching (немедленная выборка): ассоциация, коллекция или атрибут извлекаются сразу после загрузки владельца.
- Lazy collection fetching (ленивая выборка коллекции): коллекция извлекается, когда приложение вызывает операцию над этой коллекцией. Это значение по умолчанию для коллекций.
- «Extra-lazy» collection fetching («очень» ленивая выборка коллекции): индивидуальные элементы коллекции доступны из базы данных по мере необходимости. Hibernate пытается не собирать всю коллекцию в память, если только это абсолютно необходимо. Он подходит для больших коллекций.
- Proxy fetching (выборка прокси): однозначная связь выбирается, когда у связанного объекта вызывается метод, отличный от получающего идентификатор (identifier getter).
- «No-proxy» fetching (выборка «без прокси»): при доступе к переменной экземпляра выбирается однозначная связь. По сравнению с выборкой прокси, этот подход менее ленив; ассоциация выбирается, даже если доступен только идентификатор. Он также более прозрачен, поскольку отсутствие прокси отображается в приложении. Для этого подхода требуется инструментарий байт-кода времени сборки и редко необходим.
Здесь мы имеем два ортогональных понятия: когда выбирается ассоциация и как она
выбирается. Важно, чтобы вы не путали их. Мы используем fetch
для настройки
производительности. Мы можем использовать lazy
, чтобы определить контракт, для которого
данные всегда доступны в любом отсоединённом экземпляре определенного класса.
20.1.1. Работа с ленивыми ассоциациями
По умолчанию Hibernate использует ленивый выборку для коллекций и ленивый выбор прокси для однозначных ассоциаций. Эти значения по умолчанию имеют смысл для большинства ассоциаций в большинстве приложений.
Если вы установите hibernate.default_batch_fetch_size
, Hibernate будет использовать
оптимизацию пакетной выборки для ленивой выборки. Эта оптимизация также может быть включена на более
узком уровне.
Имейте в виду, что доступ к ленивой ассоциации вне контекста открытой сессии Hibernate приведет к исключению. Например:
s = sessions.openSession(); Transaction tx = s.beginTransaction();
User u = (User) s.createQuery("from User u where u.name=:userName") .setString("userName", userName).uniqueResult(); Map permissions = u.getPermissions();
tx.commit(); s.close();
Integer accessLevel = (Integer) permissions.get("accounts"); // Ошибка!
Поскольку коллекция разрешений не была инициализирована после закрытия Session
, коллекция
не сможет загрузить его состояние.
Hibernate не поддерживает ленивую инициализацию для отсоединённых объектов
. Это можно устранить переместив код, который читает из коллекции, в место перед тем,
как транзакция будет зафиксирована (commit).
Кроме того, вы можете использовать «нелинивую» коллекцию или ассоциацию, указав
lazy=«false»
для отображения ассоциации. Однако предполагается, что ленивая
инициализация будет использоваться практически для всех коллекций и ассоциаций. Если вы определяете
слишком много «нелинивых» ассоциаций в своей объектной модели, Hibernate будет извлекать
всю базу данных в память в каждой транзакции.
С другой стороны, вы можете использовать join fetching (связывающую выборку), которая по своей природе не является ленивой, вместо select fetching (выборки запросом) в конкретной транзакции. Теперь мы объясним, как настроить стратегию выборки. В Hibernate механизмы выбора стратегии выборки идентичны для однозначных ассоциаций и коллекций.
20.1.2. Настройка стратегий выборки
Select fetching (выборка запросом) (по умолчанию) чрезвычайно неэффективна из-за проблемы «N + 1 selects» (количества select, равное N + 1), поэтому мы захотим включить join fetching (связывающую выборку) в документе отображения:
<set name="permissions" fetch="join"> <key column="userId"/> <one-to-many class="Permission"/> </set
<many-to-one name="mother" class="Cat" fetch="join"/>
Стратегия выборки (fetch
), определенная в документе отображения, влияет на:
- поиск через
get()
илиload()
- поиск, который происходит неявно при навигации по ассоциации
- запросы
Criteria
- запросы HQL, если используется выборка
subselect
Независимо от используемой стратегии выборки, определённый «неленивый» граф гарантируемо будет загружен в память. Это может, однако, привести к нескольким немедленным select-запросам, которые используются для выполнения конкретного запроса HQL.
Обычно, документ отображения не используется для настройки выборки. Вместо этого мы сохраняем
поведение по умолчанию и переопределяем его для конкретной транзакции, используя выборку
с left join
в HQL. Это говорит Hibernate получить ассоциацию быстро,
в первом select-запросе, используя outer join. В API запросов Criteria
вы должны
использовать setFetchMode(FetchMode.JOIN)
.
Если вы хотите изменить стратегию выборки, используемую get()
или load()
,
вы можете использовать запрос Criteria
. Например:
User user = (User) session.createCriteria(User.class) .setFetchMode("permissions", FetchMode.JOIN) .add( Restrictions.idEq(userId) ) .uniqueResult();
Это эквивалент Hibernate того, что некоторые решения ORM называют «планом извлечения (fetch plan)».
Совершенно иной подход к проблемам с «N + 1 selects» — использовать кэш второго уровня.
20.1.3. Одиносторонняя ассоциация прокси
Ленивая выборка для коллекций осуществляется с использованием собственной реализации постоянных коллекций Hibernate. Однако для ленивого поведения в односторонних ассоциациях необходим другой механизм. Целевая сущность ассоциации должен быть проксирована. Hibernate реализует ленивые инициализирующие прокси для постоянных объектов, используя усовершенствование байт-кода во время выполнения, доступ к которому осуществляется через поставщика байт-кода.
При запуске Hibernate генерирует прокси по умолчанию для всех постоянных классов и использует их для включения ленивой выборки ассоциаций «многие-к-одному» и «один-к-одному».
Файл отображения может объявить интерфейс для использования в качестве прокси-интерфейса для этого класса
с атрибутом proxy
. По умолчанию Hibernate использует подкласс класса.
Проксируемый класс должен реализовывать конструктор по умолчанию с областью видимости пакета
как минимум. Этот конструктор рекомендуется для всех постоянных классов.
Существуют потенциальные проблемы, которые следует учитывать при распространении этого подхода на полиморфные классы. Например:
<class name="Cat" proxy="Cat"> ...... <subclass name="DomesticCat"> ..... </subclass> </class>
Во-первых, экземпляры Cat
никогда не будут приводимы к DomesticCat
, даже если
базовый экземпляр является экземпляром DomesticCat
:
Cat cat = (Cat) session.load(Cat.class, id); // создать экземпляр прокси (не дёргая db) if ( cat.isDomesticCat() ) { // дёрнуть db, чтобы инициализировать прокси-сервер DomesticCat dc = (DomesticCat) cat; // Ошибка! .... }
Во-вторых, возможно разбить прокси ==
:
Cat cat = (Cat) session.load(Cat.class, id); // создать экземпляр прокси Cat DomesticCat dc = (DomesticCat) session.load(DomesticCat.class, id); // получить новый прокси DomesticCat! System.out.println(cat == dc); // false
Однако ситуация не такая уж плохая, как кажется. Несмотря на то, что теперь у нас есть две ссылки на разные прокси-объекты, базовый экземпляр по-прежнему будет одним и тем же объектом:
cat.setWeight(11.0); // дёрнуть db, чтобы инициализировать прокси System.out.println( dc.getWeight() ); // 11.0
В-третьих, вы не можете использовать поставщик байт-кода, генерируемый прокси, для final-класса или класса с любыми final-методами.
Наконец, если ваш постоянный объект получает какие-либо ресурсы после создания экземпляра (например, в инициализаторах или конструкторе по умолчанию), эти ресурсы также будут получены прокси. Прокси-класс является фактическим подклассом постоянного класса.
Все эти проблемы связаны с фундаментальными ограничениями в модели одиночного наследования Java.
Чтобы избежать этих проблем, ваши постоянные классы должны реализовывать интерфейс, который объявляет свои
бизнес-методы. Вы должны указать эти интерфейсы в файле отображения, где CatImpl
реализует интерфейс Cat
и DomesticCatImpl
реализует интерфейс
DomesticCat
. Например:
<class name="CatImpl" proxy="Cat"> ...... <subclass name="DomesticCatImpl" proxy="DomesticCat"> ..... </subclass> </class>
Затем прокси для экземпляров Cat
и DomesticCat
могут быть возвращены методами
load()
или iterate()
.
Cat cat = (Cat) session.load(CatImpl.class, catid); Iterator iter = session.createQuery("from CatImpl as cat where cat.name='fritz'").iterate(); Cat fritz = (Cat) iter.next();
Заметка
list()
обычно не возвращает прокси.
Отношения также лениво инициализируются. Это значит вы должны объявлять какие-либо свойства типа
Cat
, а не CatImpl
.
Некоторые операций не требуют инициализации прокси:
-
equals()
: если постоянный класс не переопределяетequals()
-
hashCode()
: если постоянный класс не переопределяетhashCode()
- метод, получающий идентификатор
Hibernate будет обнаруживать постоянные классы, которые переопределяют equals()
или hashCode()
.
Выбрав lazy=«no-proxy»
вместо стандартного lazy=«proxy»
,
вы можете избежать проблем, связанных с приведением типов. Тем не менее, инструментарий
байт-кода времени сборки потребуется, и все операции приведут к немедленной инициализации
прокси.
20.1.4. Инициализация коллекций и прокси
Исключение LazyInitializationException
будет выброшено Hibernate, если доступ
к неинициализированной коллекции или прокси будет осуществляться извне области видимости
Session
, то есть когда сущность, владеющая коллекцией или имеющая ссылку на прокси,
находится в отсоединённом состоянии.
Иногда перед закрытием Session
необходимо инициализировать прокси или коллекцию. Вы можете
принудительно инициализировать их вызывом cat.getSex()
или cat.getKittens().size()
,
например. Однако это может ввести в заблуждение читателей кода, и это не удобно для общего кода.
Статические методы Hibernate.initialize()
и Hibernate.isInitialized()
обеспечивают приложение удобным способом работы с лениво инициализированными коллекциями или прокси.
Hibernate.initialize(cat)
заставит инициализировать прокси, если его Session
всё
еще открыт. Hibernate.initialize(cat.getKittens())
имеет аналогичный эффект для коллекции котят.
Другой вариант — держать Session
открытым до тех пор, пока не будут загружены
все необходимые коллекции и прокси. В некоторых архитектурах приложений, особенно в тех случаях,
когда код, который обращается к данным с использованием Hibernate, и код, который его
использует, находятся в разных прикладных слоях или разных физических процессах, может быть проблемой
обеспечить, чтобы Session
был открыт при инициализации коллекции. Существует два основных способа
решения этой проблемы:
-
В веб-приложении фильтр сервлетов может использоваться для закрытия
Session
только в конце пользовательского запроса после завершения рендеринга представления (шаблон «Open Session in View»). Конечно, это предъявляет высокие требования к правильности обработки исключений вашей инфраструктурой приложения. Крайне важно, чтобыSession
был закрыт, и транзакция закончилась, прежде чем вернуться к пользователю, даже когда во время рендеринга представления возникает исключение. См. Hibernate Wiki с примерами шаблона «Open Session in View». -
В приложении с отдельным бизнес-слоем бизнес-логика должна «подготовить» все коллекции, которые потребуются веб-слоею до их возвращения. Это означает, что бизнес-слой должен загружать все данные и возвращать их уже инициализированными на слой представления/веб, который требуется для конкретного случая использования. Обычно приложение вызывает
Hibernate.initialize()
для каждой коллекции, которая будет необходима в веб-слое (этот вызов должен быть выполнен до закрытия сессии) или получает коллекцию с помощью Hibernate-запроса с секциейFETCH
илиFetchMode.JOIN
вCriteria
. Будет проще, если вы используете шаблон Command вместо Session Facade. -
Вы также можете присоединить ранее загруженный объект к новому
Session
вызовомmerge()
илиlock()
перед доступом к неинициализированным коллекциям или другим прокси. Hibernate не делает и, конечно же, не должен делать это автоматически, так как он вводит импровизированную семантику транзакций.
Иногда вам не захочется инициализировать большую коллекцию, но по-прежнему нужна будет некоторая информации о ней: например, о ее размере или подмножестве данных.
Вы можете использовать фильтр коллекции, чтобы получить размер коллекции без ее инициализации:
( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()
Метод createFilter()
также используется для эффективного извлечения подмножеств коллекции
без необходимости инициализации всей коллекции:
s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();
20.1.5. Использование пакетной выборки
Используя пакетную выборку, Hibernate может загружать несколько неинициализированных прокси, если к одину прокси есть обращаение. Пакетная выборка — это оптимизация стратегии ленивой выборки. Существует два способа настройки пакетной выборки: на уровне класса и уровне коллекции.
Пакетную выборку для классов/сущностей легче понять. Рассмотрим следующий пример: во время выполнения
у вас есть 25 экземпляров Cat
, загруженных в Session
, и каждый
Cat
имеет ссылку на owner
— типа Person
.
Класс Person
отображается на прокси, lazy=«true»
.
Если вы теперь перебираете всех Cat
и вызываете getOwner()
на каждом,
Hibernate по умолчанию выполнит 25 иструкций SELECT
для извлечения owner
.
Вы можете настроить это поведение, указав размер пакета batch-size
в отображении
Person
:
<class name="Person" batch-size="10">...</class>
Если batch-size
указан, Hibernate будет выполнять запросы по требованию, когда необходимо
получить доступ к неинициализированному прокси, как указано выше, но разница заключается в том,
что вместо того, чтобы запрашивать конкретную прокси-сущность, к которой обращаются, он будет
запрашивать больше сущностей за один раз, поэтому при доступе к owner
другого прокси
он уже может быть инициализирован этой пакетной выборкой, и будет выполнено только несколько
запросов (гораздо меньше 25).
Такое поведение контролируется конфигурацией batch-size
и стилем пакетной выборки.
Конфигурация стиля пакетной выборки (hibernate.batch_fetch_style
) является новым улучшением
производительности с 4.2.0. Есть три различные стратегии: LEGACY
, PADDED
и DYNAMIC
.
-
LEGACY
Унаследованный алгоритм, в котором мы сохраняем набор готовых размеров пакета на основе
org.hibernate.internal.util.collections.ArrayHelper#getBatchSizes
. Каждый следующий выполняющийся пакет имеет меньший размера готового пакета из числа существующих идентификаторов пакетов.В приведенном выше примере, с размером пакета установленным в 25, размер предварительной сборки был бы [25, 12, 10, 9, 8, 7, .., 1].
И так как есть владелец 25 персон, который будет инициализирован, тогда только этот запрос будет выполнен с использованием этого идентификатора 25 владельца.
Но в другом случае, предположим, что есть только 24 персоны, будет выполнено 3 запроса (12, 10, 2), чтобы пройти через владельцев всех персон, и запрос будет выглядеть так:
select * from owner where id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) select * from owner where id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) select * from owner where id in (?, ?)
-
PADDED
Это похоже на унаследованный алгоритм, он использует размеры предварительной сборки, основанные на том же
org.hibernate.internal.util.collections.ArrayHelper#getBatchSizes
. Разница заключается в том, что здесь hibernate будет использовать следующим пакет с бОльшим размером и заполнит дополнительные заполнители идентификаторов.Таким образом, используя приведённый выше пример инициализиции 25 персон, запрос будет таким же, только один запрос будет выполнен для пакетного запроса всех владельцев.
Тем не менее, попытка загрузки пакета с загрузкой 24 владельцев привела бы к одному пакету размером 25, идентификаторы для загрузки были бы «дополнены» (повторены), чтобы составить разницу.
-
DYNAMIC
Динамически строит свой SQL на основе фактического количества доступных идентификаторов. Всё еще ограничивает размер пакета, определённый для сущности.
Вы также можете включить пакетную выборку коллекций. Например, если у каждого Person
есть ленивая коллекция Cat
, а в Session
загружается 10 персон,
итерация через всех персон будет генерировать 10 SELECT
, по одному для каждого вызова
getCats()
. Если вы включите пакетную выборку для коллекции cats
при отображении
Person
, Hibernate может предварительно выбрать коллекции:
<class name="Person"> <set name="cats" batch-size="3"> ... </set> </class>
Например, с batch-size
3 и использованием legacy
пакетного стиля,
Hibernate будет загружать 3, 3, 3, 1 коллекции в четыре SELECT
. Опять же, значение
атрибута зависит от ожидаемого количества неинициализированных коллекций в конкретном
Session
.
Пакетная выборка коллекций особенно полезна, если у вас есть вложенное дерево элементов, т. е. типичный шаблон «Биль материалов (bill-of-materials)». Однако вложенный набор или материализованный путь может быть лучшим вариантом для деревьев с преобладанием чтения.
20.1.6. Использование выборки подзапросов
Если требуется использовать ленивую коллекцию или однозначный прокси Hibernate загрузит все из них, повторно выполнив исходный запрос в подзапросе (subselect). Это работает так же, как и пакетная выборка, но без поэтапной загрузки.
20.1.7. Выборка профилей
Еще один способ повлиять на стратегию выборки для загрузки связанных объектов — это то,
что называется выборка профиля, который является именованной конфигурацией, связанной
с org.hibernate.SessionFactory
, но включённой по имени
в org.hibernate.Session
. После включения в org.hibernate.Session
,
профиль выборки будет влиять на этот org.hibernate.Session
, пока он не будет явно
отключен.
Так что это значит? Объясним это что на примере, показывающем различные доступные подходы к настройке профиля выборки:
Пример 20.1. Указание профиля выборки с использованием @FetchProfile
@Entity @FetchProfile(name = "customer-with-orders", fetchOverrides = { @FetchProfile.FetchOverride(entity = Customer.class, association = "orders", mode = FetchMode.JOIN) }) public class Customer { @Id @GeneratedValue private long id;
private String name;
private long customerNumber;
@OneToMany private Set<Order> orders; // стандартные getter/setter ... }
Пример 20.2. Указание профиля выборки с использованием <fetch-profile> вне узла <class>
<hibernate-mapping> <class name="Customer"> ... <set name="orders" inverse="true"> <key column="cust_id"/> <one-to-many class="Order"/> </set> </class> <class name="Order"> ... </class> <fetch-profile name="customer-with-orders"> <fetch entity="Customer" association="orders" style="join"/> </fetch-profile> </hibernate-mapping>
Пример 20.3. Указание профиля выборки с использованием <fetch-profile> внутри узла <class>
<hibernate-mapping> <class name="Customer"> ... <set name="orders" inverse="true"> <key column="cust_id"/> <one-to-many class="Order"/> </set> <fetch-profile name="customer-with-orders"> <fetch association="orders" style="join"/> </fetch-profile> </class> <class name="Order"> ... </class> </hibernate-mapping>
Теперь, когда вы получаете ссылку на конкретного клиента, набор заказов этого клиента будет ленивым, означающим, что мы еще не загрузили эти заказы из базы данных. Обычно это хорошо. Теперь давайте скажем, что у вас есть такой случай, когда более эффективно загружать клиента и его заказы вместе. Одним из способов является использование стратегий «динамической выборки» с помощью запросов HQL или Criteria . Но другой вариант для достижения этого — использовать профиль выборки. Следующий код загрузит как клиента, так и его заказы:
Пример 20.4. Активация профиля выборки для данного Session
Session session = ...; session.enableFetchProfile( "customer-with-orders" ); // name matches from mapping Customer customer = (Customer) session.get( Customer.class, customerId );
Заметка
Определения @FetchProfile
являются глобальными, и неважно, на каком классе
вы их размещаете. Вы можете поместить аннотацию @FetchProfile
либо в класс,
либо в пакет (package-info.java). Чтобы определить несколько профилей извлечения для того же
класса или пакета, можно использовать @FetchProfiles
.
20.1.8. Использование ленивой выборки свойств
Hibernate поддерживает ленивый выборку отдельных свойств. Этот техника оптимизации также известна как выборка групп. Обратите внимание, что это в основном маркетинговый ход; оптимизация чтения строк гораздо важнее оптимизации чтения столбцов. Тем не менее, только загрузка некоторых свойств класса может быть полезна в крайних случаях. Например, когда унаследованные таблицы содержат сотни столбцов, а модель данных не может быть улучшена.
Чтобы включить ленивую загрузку свойств, установите атрибут lazy
в конкретных свойствах
отображений:
<class name="Document"> <id name="id"> <generator class="native"/> </id> <property name="name" not-null="true" length="50"/> <property name="summary" not-null="true" length="200" lazy="true"/> <property name="text" not-null="true" length="2000" lazy="true"/> </class>
Ленивая загрузка свойств требует использования инструментов байт-кода времени сборки. Если ваши постоянные классы не будут расширены, Hibernate будет игнорировать настройки ленивых свойств и вернёться к немедленной выборке.
Для использования инструментов байт-кода используйте следующую задачу Ant:
<target name="instrument" depends="compile"> <taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask"> <classpath path="${jar.path}"/> <classpath path="${classes.dir}"/> <classpath refxml:id="lib.class.path"/> </taskdef> <instrument verbose="true"> <fileset dir="${testclasses.dir}/org/hibernate/auction/model"> <include name="*.class"/> </fileset> </instrument> </target>
Другой способ избежать ненужного чтения столбцов, по крайней мере для только «читающих» транзакций, заключается в использовании особенностей запросов HQL или Criteria. Это позволяет избежать необходимости обработки байт-кода во время сборки и, безусловно, является предпочтительным решением.
Вы можете форсировать обычную «нетерпеливую (eager)» выборку свойств, используя
fetch all properties
в HQL.
20.2. Кэш второго уровня
Hibernate Session
представляет собой кэш уровня транзакции постоянных данных. Можно настроить
кэш кластера или уровня JVM (SessionFactory
-уровня) для каждого класса и коллекции
по отдельности. Вы даже можете подключить кластерный кэш. Имейте в виду, что кэши не знают
об изменениях, вносимых в постоянное хранилище другим приложением. Тем не менее, они могут быть
настроены на регулярное истечение актуальности кэшированных данных.
Таблица 20.1. Поставщики кэша
Кэш | Класс поставщика | Тип | Кластерная безопастность | Поддерживается кэш запросов |
---|---|---|---|---|
ConcurrentHashMap (только для целей тестирования, в модуле hibernate-testing) | org.hibernate.testing.cache.CachingRegionFactory | память | да | |
EHCache | org.hibernate.cache.ehcache.EhCacheRegionFactory | память, диск, транзакционный, кластерный | да | да |
Infinispan | org.hibernate.cache.infinispan.InfinispanRegionFactory | транзакционный, кластерный (ip multicast) | да (репликация или инвалидация) | да (clock sync req.) |
20.2.1. Отображения кэшей
Как мы уже говорили в предыдущих главах, мы рассматриваем две различные возможности настройки кэширования. Первая конфигурация через аннотации, а затем через файлы отображения Hibernate.
По умолчанию сущности не входят в кэш второго уровня, и мы рекомендуем придерживаться
этого параметра. Однако вы можете переопределить это, установив элемент shared-cache-mode
в файле persistence.xml
или используя свойство javax.persistence.sharedCache.mode
в вашей конфигурации. Возможны следующие значения:
-
ENABLE_SELECTIVE
(значение по умолчанию и рекомендуемое значение): сущности не кэшируются, если явно не помечены как кэшируемые. -
DISABLE_SELECTIVE
: сущности кэшируются, если явно не отмечены как не кэшируемые. -
ALL
: все сущности всегда кэшируются, даже если они помечены как не кэшируемые. -
NONE
: никакая сущность не кэшируется, даже если она помечена как кэшируемая. Этот параметр может иметь смысл для отключения кэша второго уровня.
Стратегия параллельного использования кэша, используемая по умолчанию, может быть установлена глобально
через свойство конфигурации hibernate.cache.default_cache_concurrency_strategy
.
Значения для этого свойства:
read-only
read-write
nonstrict-read-write
transactional
Заметка
Рекомендуется определять стратегию параллельного использования кэша для каждой сущности,
а не использовать глобальную. Для этого используйте аннотацию
@org.hibernate.annotations.Cache
.
Пример 20.5. Определение стратегии параллельного использования кэша через @Cache
@Entity @Cacheable @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) public class Forest { ... }
Hibernate также позволяет вам кэшировать содержимое коллекции или идентификаторы, если коллекция содержит
другие сущности. Используйте аннотацию @Cache
для свойства коллекции.
Пример 20.6. Кэширование коллекций с помощью аннотаций
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER) @JoinColumn(name="CUST_ID") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) public SortedSetgetTickets() { return tickets; }
Пример 20.7. «Аннотация @Cache
с атрибутами»
показывает аннотации @org.hibernate.annotations.Cache
с их атрибутами. Они позволяют
определить стратегию кэширования и область заданного кэша второго уровня.
Пример 20.7. Аннотация @Cache
с атрибутами
@Cache( CacheConcurrencyStrategy usage(); (1) String region() default ""; (2) String include() default "all"; (3) )
- usage: заданная стратегия параллельного использования кэша (NONE, READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL)
- region (необязательный): область кэша (по умолчанию для fqcn класса или имя роли fq коллекции)
- include (необязательный): «all» включает все свойства. «non-lazy» включает только неленивые свойства (по умолчанию «all»).
Давайте теперь посмотрим на файлы отображения Hibernate. Для настройки кэша второго уровня используется элемент <cache> для отображения классов или коллекций. При рассмотрении примера 20.8. «Hibernate элемент отображения <cache>» параллели с анотивами очевидны.
Пример 20.8. Hibernate элемент отображения <cache>
<cache usage="transactional|read-write|nonstrict-read-write|read-only" (1) region="RegionName" (2) include="all|non-lazy" /> (3)
-
usage (обязательный): указывает стратегию кэширования:
transactional
,read-write
,nonstrict-read-write
илиread-only
- region (необязательный: по умолчанию — имя класса или коллекции): указывает имя области кэша второго уровня
-
include (необязательный: по умолчанию —
all
)non-lazy
: указывает, что свойства сущности, отображаемой с помощьюlazy=«true»
, не могут быть кэшированы, когда включена ленивая выборка уровня атрибута
В качестве альтернативы <cache> вы можете использовать элементы <class-cache>
и <collection-cache> в файле hibernate.cfg.xml
.
Давайте теперь рассмотрим различные стратегии использования.
20.2.2. Стратегия: только для чтения
Если ваше приложение должно читать, но не изменять экземпляры постоянного класса, можно использовать
кэш только для чтения read-only
. Это простейшая и оптимальная стратегия исполнения.
Она даже безопасна для использования в кластере.
20.2.3. Стратегия: чтение/запись
Если приложение должно обновлять данные, может потребоваться кэш чтения-записи read-write
.
Эта стратегия кэширования никогда не должна использоваться, если требуется сериализованный уровень
изоляции транзакций. Если кэш используется в среде JTA, вы должны указать свойство
hibernate.transaction.manager_lookup_class
и называть стратегию для получения JTA
TransactionManager
. В других средах вы должны убедиться, что транзакция завершена
когда вызоваются Session.close()
или Session.disconnect()
. Если вы хотите
использовать эту стратегию в кластере, вы должны убедиться, что реализация кэша поддерживает
блокировку. Встроенные поставщики кэша не поддерживают блокировку.
20.2.4. Стратегия: нестрогое чтение/запись
Если приложение только изредка нуждается в обновлении данных (т. е. если крайне маловероятно,
что две транзакции будут пытаться одновременно обновлять один и тот же элемент), а строгая
изоляция транзакций не требуется, может потребоваться кэш нестрогого чтения-записи
nonstrict-read-write
. Если кэш используется в среде JTA, вы должны указать
hibernate.transaction.manager_lookup_class
. В других средах вы должны убедиться,
что транзакция завершена когда вызоваются Session.close()
или Session.disconnect()
.
20.2.5. Стратегия: транзакционная
Стратегия транзакционного (transactional
) кэширования обеспечивает поддержку полностью
транзакционных поставщиков кэширования, таких как JBoss TreeCache. Такой кэш может использоваться только
в среде JTA, и вы должны указать hibernate.transaction.manager_lookup_class
.
20.2.6. Совместимость с поставщиком кэша/параллельной стратегией
В следующей таблице показано, какие поставщики совместимы со стратегиями параллельного использования.
Таблица 20.2. Поддержка стратегии параллельного кэша
Кэш | read-only | nonstrict-read-write | read-write | transactional |
---|---|---|---|---|
ConcurrentHashMap (не предназначен для использования в производстве) | да | да | да | |
EHCache | да | да | да | да |
Infinispan | да | да |
20.3. Управление кэшами
Всякий раз, когда вы передаете объект для операций save()
, update()
или saveOrUpdate()
, и всякий раз, когда вы извлекаете объект с помощью
load()
, get()
, list()
, iterate()
или scroll()
,
этот объект добавляется во внутренний кэш Session
.
Когда вызывается flush()
, состояние этого объекта будет синхронизироваться с базой данных.
Если вы не хотите, чтобы эта синхронизация произошла, или если вы обрабатываете огромное
количество объектов и должны эффективно управлять памятью, метод evict()
может быть
использован для удаления объекта и его коллекций из кэша первого уровня.
Пример 20.9. Исключительное вытеснение экземпляра из кэша первого уровня с использованием
Session.evict()
ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set while ( cats.next() ) { Cat cat = (Cat) cats.get(0); doSomethingWithACat(cat); sess.evict(cat); }
Session
также предоставляет метод contains()
для определения того,
принадлежит ли экземпляр кэшу сессии.
Чтобы вытеснить все объекты из кэша сессии, вызовите Session.clear()
.
Для кэша второго уровня существуют методы, определенные в SessionFactory
для вытеснения
кэшированного состояния экземпляра, всего класса, экземпляра коллекции или всей коллекции.
Пример 20.10. Вытеснение кэша второго уровня через SessionFactoty.evict()
и SessionFacyory.evictCollection()
sessionFactory.evict(Cat.class, catId); //вытеснить определённый Cat sessionFactory.evict(Cat.class); //вытеснить все Cats sessionFactory.evictCollection("Cat.kittens", catId); //вытеснить определённую коллекцию kittens sessionFactory.evictCollection("Cat.kittens"); //вытеснить все коллекции kitten
CacheMode
управляет тем, как определённая сессия взаимодействует с кэшем второго уровня:
-
CacheMode.NORMAL
: считывает элементы и записывает элементы в кэш второго уровня. -
CacheMode.GET
: будет читать элементы из кэша второго уровня. Не пишите в кэш второго уровня кроме случаев обновления данных. -
CacheMode.PUT
: записывает элементы в кэш второго уровня. Не читайте из кэша второго уровня. -
CacheMode.REFRESH
: записывает элементы в кэш второго уровня. Не читайте из кэша второго уровня. Обход эффектаhibernate.cache.use_minimal_puts
, заставляющий обновлять кэш второго уровня для всех элементов, считанных из базы данных.
Чтобы просмотреть содержимое области второго уровня или кэша запросов, используйте API статистики:
Пример 20.11. Просмотр записей кэша второго уровня через Statistics
API
Map cacheEntries = sessionFactory.getStatistics() .getSecondLevelCacheStatistics(regionName) .getEntries();
Вам нужно будет включить статистику и (необязательно) заставить Hibernate сохранять записи кэша в более читаемом формате:
Пример 20.12. Включение статистики Hibernate
hibernate.generate_statistics true hibernate.cache.use_structured_entries true
20.4. Кэш запросов
Наборы результатов запроса также можно кэшировать. Это полезно только для запросов, которые часто запускаются с одинаковыми параметрами.
20.4.1. Включение кэширования запросов
Кэширование результатов запроса приводит к некоторым накладным расходам с точки зрения обычной
транзакционной обработки ваших приложений. Например, если вы кэшируете результаты запроса Person
Hibernate, вам нужно будет отслеживать, когда эти результаты будут признаны недействительными, поскольку
Person
изменился. В сочетании с тем фактом, что большинство приложений просто
не получают выгоды от кэширования результатов запроса, приводит Hibernate к отключению кэширования
результатов запроса по умолчанию. Чтобы использовать кэширование запросов, вам сначала необходимо включить
кэш запросов:
hibernate.cache.use_query_cache true
Этот параметр создает две новые области кэша:
-
org.hibernate.cache.internal.StandardQueryCache
, содержащий результаты кэшированного запроса. -
org.hibernate.cache.spi.UpdateTimestampsCache
, содержащий временные метки последних обновлений для запрашиваемых таблиц. Они используются для проверки результатов, поскольку они обслуживаются из кэша запросов.
Важно
Если вы сконфигурируете реализацию основного кэша для использования истечения срока действия или
тайм-аутов, очень важно, чтобы тайм-аут кэша для кэш-региона для UpdateTimestampsCache
был установлен на большее значение, чем таймауты любого из кэшей запросов. На самом деле
мы рекомендуем, чтобы регион UpdateTimestampsCache
не был настроен для истечения
срока действия. Обратите внимание, в частности, что политика истечения срока действия LRU никогда
не подходит.
Как упоминалось выше, большинство запросов не извлекают выгоду из кэширования или их результатов.
Таким образом, по умолчанию отдельные запросы не кэшируются даже после включения кэширования запросов.
Чтобы включить кэширование результатов для конкретного запроса, вызовите
org.hibernate.Query.setCacheable(true)
. Этот вызов позволяет запросить поиск существующих результатов
кэша или добавить его результаты в кэш при его выполнении.
Заметка
Кэш запросов не кэширует состояние фактических сущностей в кэше; он кэширует только значения идентификатора и результаты типа значения. По этой причине кэш запросов всегда должен использоваться в сочетании с кэшем второго уровня для тех сущностей, которые, как ожидается, будут кэшироваться как часть кэша результатов запроса (как и при кэшировании коллекции).
20.4.2. Области кэша запросов
Если вам требуется микроконтроль политик истечения срока действия кэша запросов, вы можете указать область
именованного кэша для конкретного запроса, вызвав Query.setCacheRegion()
.
List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger") .setEntity("blogger", blogger) .setMaxResults(15) .setCacheable(true) .setCacheRegion("frontpages") .list();
Если вы хотите заставить кэш запросов обновить один из его регионов (не обращайте внимания
на какие-либо результаты кэширования, которые он находит там), вы можете использовать
org.hibernate.Query.setCacheMode(CacheMode.REFRESH)
. В сочетании с областью, определенной
для данного запроса, Hibernate будет выборочно принудительно обновлять результаты, кэшированные в этой
конкретной области. Это особенно полезно в тех случаях, когда базовые данные могут быть обновлены через
отдельный процесс и являются гораздо более эффективной альтернативой массовому вытеснению региона через
org.hibernate.SessionFactory.evictQueries()
.
20.5. Улучшение байткода
Hibernate внутренне нуждается в вхождении (entry) (org.hibernate.engine.spi.EntityEntry
), чтобы
сообщить текущее состояние объекта относительно его постоянного состояния, когда объект связан
с Session
. Тем не менее, поддержание этой ассоциации было довольно тяжелой операцией из-за
большого количества других правил, которые должны применяться, поскольку с 4.2.0 существует новое
усовершенствование, предназначенное для этой цели, что уменьшит связанные с сессией память и перегрузки
процессора.
В основном, идея состоит в том, что вместо того, чтобы иметь настроенную (вид тяжелой и обычно обозначаемый как «горячая точка») карту, чтобы посмотреть вверх, мы меняем ее на
EntityEntry entry = (ManagedEntity)entity.$$_hibernate_getEntityEntry();
Существует три способа получить преимущества от этого нового улучшения:
20.5.1. Реализация интерфейса org.hibernate.engine.spi.ManagedEntity
Сущность может сама реализовать этот интерфейс, тогда ответственность за поддержание би-ассоциации,
по существу, обеспечивает доступ к информации об ассоциации экземпляра
с Session/EntityManager. Подробнее о org.hibernate.engine.spi.ManagedEntity
можно узнать
из javadoc.
20.5.2. Инструмент времени выполнения (Runtime)
Иногда вы, вероятно, не захотите внедрять интрузивный интерфейс, возможно, из-за переносимости, что отлично, и Hibernate позаботится об этом с помощью класса-оболочки, который реализует этот интерфейс, а также внутреннего кэша, который отображает этот экземпляр сущности и обертку вместе.
Очевидно, что это самый простой способ выбора, поскольку он не требует изменения исходного кода проекта, но он также требует больше памяти и использования CPU по сравнению с первым.
20.5.3. Инструмент времени построения (Build-time)
Помимо вышеупомянутых двух подходов, Hibernate также предоставляет третий вариант, который улучшает время
построения байт-кода. Приложения могут использовать расширенные классы сущностей, аннотированные с помощью
javax.persistence.Entity
или составного javax.persistence.Embeddable
.
Ant Task
Чтобы использовать задачу org.hibernate.tool.enhance.EnhancementTask
, определите taskdef
и вызовите задачу, как показано ниже. Этот код использует предопределённый classpathref
и свойство, ссылающееся на каталог скомпилированных классов.
<taskdef name="enhance" classname="org.hibernate.tool.enhance.EnhancementTask" classpathref="enhancement.classpath" /> <enhance> <fileset dir="${ejb-classes}/org/hibernate/auction/model" includes="**/*.class"/> </enhance>
Заметка
EnhancementTask
предназначен для полной замены InstrumentTask
. Кроме того,
он также несовместим с InstrumentTask
, поэтому любые существующие инструментальные
классы необходимо будет снова собрать из источника.
Maven плагин
Плагин Maven использует дескриптор Mojo для присоединения Mojo к фазе компиляции вашего проекта.
<dependencies> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-[SPEC-VERSION]-api</artifactId> <version>[IMPL-VERSION]</version> <scope>compile</scope> </dependency> </dependencies> <plugins> <plugin> <groupId>org.hibernate.orm.tooling</groupId> <artifactId>hibernate-enhance-maven-plugin</artifactId> <version>VERSION</version> <executions> <execution> <goals> <goal>enhance</goal> </goals> </execution> </executions> </plugin>
Gradle плагин
Плагин Gradle добавляет задачу улучшения, используя выходной каталог задачи компиляции в качестве исходного расположения файлов классов сущностей для улучшения.
apply plugin: 'java' apply plugin: 'maven' apply plugin: 'hibernate' buildscript { repositories { mavenCentral() } dependencies { classpath 'org.hibernate:hibernate-gradle-plugin:VERSION' } } dependencies { compile group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-[SPEC-VERSION]-api', version: '[IMPL-VERSION]' }
20.6. Понимание производительности коллекций
В предыдущих разделах мы рассмотрели коллекции и их приложения. В этом разделе мы рассмотрим еще несколько проблем в отношении коллекций во время выполнения.
20.6.1. Таксономия
Hibernate определяет три основных типа коллекций:
- коллекции значений
- ассоциации «один-ко-многим»
- ассоциации «многих-ко-многим»
Эта классификация отличает различные таблицы и отношения внешних ключей, но не говорит нам совершенно всё, что нам нужно знать о реляционной модели. Чтобы полностью понять реляционную структуру и характеристики производительности, мы также должны рассмотреть структуру первичного ключа, который используется Hibernate для обновления или удаления записей коллекции. Это предполагает следующую классификацию:
- индексированные коллекции
- наборы (множества (sets))
- сумки (bags)
Все индексированные коллекции (карты, списки и массивы) имеют первичный ключ, состоящий из столбцов <key> и <index>. В этом случае обновления коллекции чрезвычайно эффективны. Первичный ключ может быть эффективно проиндексирован, и определённая строка может быть эффективно размещена, когда Hibernate пытается обновить или удалить её.
У наборов есть первичный ключ, состоящий из столбцов <key> и element. Это может быть
менее эффективным для некоторых типов элементов коллекции, особенно составных элементов или больших текстовых
или двоичных полей, поскольку база данных может не иметь возможности индексировать сложный первичный ключ
эффективно. Однако для ассоциаций «один-ко-многим» или «многие-ко-многим», особенно
в случае синтетических идентификаторов, он, вероятно, будет столь же эффективным. Если вы хотите,
чтобы SchemaExport
фактически создал первичный ключ <set>, вы должны объявить все
столбцы как not-null=«true»
.
<idbag> отображения определяют суррогатный ключ, поэтому они эффективны для обновления. По факту, это лучший случай.
Сумки (bags) являются наихудшим случаем, так как они допускают повторяющиеся значения элементов и,
поскольку у них нет столбца индекса, первичный ключ не может быть определён. Hibernate не имеет
возможности различать повторяющиеся строки. Hibernate разрешает эту проблему, полностью удаляя в одном
DELETE
и воссоздавая коллекцию всякий раз, когда она изменяется. Это может быть неэффективным.
Для связи «один-ко-многим» «первичный ключ» может быть не физическим первичным ключом таблицы базы данных. Даже в этом случае вышеуказанная классификация по-прежнему полезна. Она отражает, как Hibernate «определяет» отдельные строки коллекции.
20.6.2. List, Map, idbag и Set — наиболее эффективные коллекции для обновления
Из приведённого выше обсуждения должно быть ясно, что индексированные коллекции и наборы позволяют выбрать наиболее эффективную операцию с точки зрения добавления, удаления и обновления элементов.
Существует, возможно, ещё одно преимущество, что индексированные коллекции имеют больше наборов
для ассоциаций «много-ко-многим» или наборов значений. Из-за структуры Set
,
Hibernate не делает UPDATE
записи, когда элемент «изменен». Изменения
в Set
всегда работают через INSERT
и DELETE
отдельных
записей. И снова это соображение не относится к ассоциациям «один-ко-многим».
Наблюдая, что массивы не могут быть ленивыми, вы можете сделать вывод, что списки (lists), карты (maps) и idbags являются наиболее эффективными (не обратными) типами коллекций, с наборами (sets) далеко не так. Вы можете ожидать, что наборы будут наиболее распространённым видом коллекции в приложениях Hibernate. Это связано с тем, что семантика «set» наиболее естественна в реляционной модели.
Однако в хорошо спроектированных моделях домена Hibernate большинство коллекций на самом деле
являются ассоциациями «один-ко-многим» с inverse=«true»
. Для этих
ассоциаций обновление обрабатывается «многими-к-одному» концом ассоциации, поэтому соображения
эффективности обновления коллекции просто не применяются.
20.6.3. Bag и List являются наиболее эффективными обратными коллекциями
Однако существует конкретный случай, когда сумки (bags), а также списки (lists), намного более эффективны,
чем наборы (sets). Для коллекции с inverse=«true»
, стандартна двунаправленная
идиома отношения «один-ко-многим», например, мы можем добавлять элементы в сумку (bag)
или список (list) без необходимости инициализации (извлечения) элементов сумки (bag). Это связано с тем,
что в отличие от набора (set) Collection.add()
или Collection.addAll()
всегда должны возвращать true для сумки (bag) или списка (List
). Следующий общий код может сделать
это намного быстрее:
Parent p = (Parent) sess.load(Parent.class, id); Child c = new Child(); c.setParent(p); p.getChildren().add(c); //no need to fetch the collection! sess.flush();
20.6.4. Удаление одним «выстрелом»
Иногда удаление элементов коллекции может быть крайне неэффективным. Hibernate не знает, что делать
в случае новой пустой коллекции (например, вы вызывали list.clear()
). В этом
случае Hibernate сделает один DELETE
.
Предположим, вы добавили один элемент в коллекцию размером 20, а затем удаляете два элемента.
Hibernate сделает одну инструкцию INSERT
и две инструкции DELETE
, если коллекция
не является сумкой (bag). Это, безусловно, желательно.
Однако предположим, что мы удалим восемнадцать элементов, оставив два, а затем добавим новые элементы. Существует два возможных способа:
- удалить восемнадцать записей по одной, а затем вставить три записи.
-
удалить всю коллекцию за один SQL
DELETE
и вставить все пять текущих элементов один за одним.
Hibernate не может знать, что второй вариант, вероятно, быстрее. Было бы нежелательно, чтобы Hibernate был настолько интуитивным, что такое поведение могло бы запутать триггеры базы данных и т. д.
К счастью, вы можете в любое время форсировать это поведение (т. е. Вторую стратегию) отбрасывать (т. е. разыменовывать) исходную коллекцию и возвращать вновь созданный набор со всеми текущими элементами.
One-shot-delete (удаление одним «выстрелом») не применяется к коллекциям, отображённым
с inverse=«true»
.
20.7. Мониторинг производительности
Оптимизация не очень удобна без мониторинга и доступа к показателям производительности.
Hibernate предоставляет полный спектр данных о своих внутренних операциях. Статистика в Hibernate
доступна на каждому SessionFactory
.
20.7.1. Мониторинг SessionFactory
Вы можете получить доступ к метрикам SessionFactory
двумя способами. Первый
вариант — вызвать sessionFactory.getStatistics()
и прочитать или отобразить
Statistics
самостоятельно.
Hibernate также может использовать JMX для публикации показателей, если вы включите MBean
StatisticsService
. Вы можете включить один MBean для всего SessionFactory
или один на фабрику. См. следующий код минимальных примеров конфигурации:
// Регистрация сервиса MBean для определённого SessionFactory Hashtable tb = new Hashtable(); tb.put("type", "statistics"); tb.put("sessionFactory", "myFinancialApp"); ObjectName on = new ObjectName("hibernate", tb); // имя объекта MBean StatisticsService stats = new StatisticsService(); // реализация MBean stats.setSessionFactory(sessionFactory); // Привязать статистики к SessionFactory server.registerMBean(stats, on); // регистрация Mbean на сервере
// Регистрация сервиса MBean для всех SessionFactory Hashtable tb = new Hashtable(); tb.put("type", "statistics"); tb.put("sessionFactory", "all"); ObjectName on = new ObjectName("hibernate", tb); // имя объекта MBean StatisticsService stats = new StatisticsService(); // реализация MBean server.registerMBean(stats, on); // регистрация Mbean на сервере
Вы можете активировать и деактивировать мониторинг для SessionFactory
:
-
во время настройки установите
hibernate.generate_statistics
в false. -
во время выполнения:
sf.getStatistics().setStatisticsEnabled(true)
илиhibernateStatsBean.setStatisticsEnabled(true)
.
Статистику можно сбросить программно с помощью метода clear()
. Сводка может быть отправлена
логгеру (уровень info ) с использованием метода logSummary()
.
20.7.2. Метрики
Hibernate предоставляет ряд показателей, от базовой информации до более специализированной информации,
которая имеет значение только в определённых сценариях. Все доступные счетчики описаны в API
интерфейса Statistics
в трех категориях:
-
Метрики, относящиеся к общему использованию
Session
, такие как количество открытых сессий, усстановленные соединениям JDBC и т. д. - Метрики, связанные с сущностями, коллекциями, запросами и кэшами в целом (также называемыми глобальными метриками).
- Подробные показатели, относящиеся к определённой сущности, коллекции, запросу или области кэша.
Например, вы можете проверить запросы к кэшу, отсутствие запрашиваемого в кэше, коэффициент кэширования сущностей, коллекций и запросов, и среднее время запроса. Имейте в виду, что количество миллисекунд может быть аппроксимировано в Java. Hibernate привязан к точности JVM, и на некоторых платформах это может быть точным только до 10 секунд.
Простые геттеры используются для доступа к глобальным метрикам (т. е. не привязаны
к конкретной сущности, коллекции, области кэша и т. д.). Вы можете получить доступ
к метрикам определённой сущности, коллекции или области кэша через её имя и через ей представление
HQL или SQL запросов. Для получения дополнительной информации см. API Javadoc Statistics
,
EntityStatistics
, CollectionStatistics
, SecondLevelCacheStatistics
и QueryStatistics
. Следующий код — простой пример:
Statistics stats = HibernateUtil.sessionFactory.getStatistics();
double queryCacheHitCount = stats.getQueryCacheHitCount(); double queryCacheMissCount = stats.getQueryCacheMissCount(); double queryCacheHitRatio = queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
log.info("Query Hit ratio:" + queryCacheHitRatio);
EntityStatistics entityStats = stats.getEntityStatistics( Cat.class.getName() ); long changes = entityStats.getInsertCount() + entityStats.getUpdateCount() + entityStats.getDeleteCount(); log.info(Cat.class.getName() + " changed " + changes + "times" );
Вы можете работать со всеми сущностями, коллекциями, запросами и областями кэшами, получая
список имён сущностей, коллекций, запросов и областей кэшей, используя следующие методы:
getQueries()
, getEntityNames()
, getCollectionRoleNames()
и getSecondLevelCacheRegionNames()
.