Skip to content

Latest commit

 

History

History
111 lines (73 loc) · 20.2 KB

File metadata and controls

111 lines (73 loc) · 20.2 KB

Оглавление

Hibernate

Какие есть кэши в Hibernate и какие работают по умолчанию?

3 уровня кеширования:

  • Кеш первого уровня (First-level cache). По умолчанию включен.
  • Кеш второго уровня (Second-level cache). По умолчанию отключен.
  • Кеш запросов (Query cache). По умолчанию отключен.

Подробнее:

Углубиться в Hibernate нам всегда поможет Vlad Mihalcea:

к содержанию

Чем отличается Lazy от Eager в Hibernate?

  • Eager Loading - стратегия загрузки, при которой подгрузка связанных сущностей происходит сразу. Для применения необходимо в аннотацию отношения (@OneToOne, @ManyToOne, @OneToMany, @ManyToMany) передать fetch = FetchType.EAGER. Используется по умолчанию для отношений @OneToOne и @ManyToOne.
  • Lazy Loading - стратегия загрузки, при которой подгрузка связанных сущностей откладывается как можно дольше. Чтобы задать такое поведение, нужно в аннотацию отношения (@OneToOne, @ManyToOne, @OneToMany, @ManyToMany) передать fetch = FetchType.LAZY. Используется по умолчанию для отношений @OneToMany, @ManyToMany. До момента загрузки используется proxy-объект, вместо реального. Если обратиться к такому LAZY-полю после закрытия сессии Hibernate, то получим LazyInitializationException.

Вопрос также связан с проблемой "N+1" и может плавно перетечь в её обсуждение.

Почитать подробнее и с примерами можно в блоге Vlad Mihalcea: раз, два и про LazyInitializationException три.

к содержанию

Что такое "проблема N+1 запроса" при использовании Hibernate? Когда возникает? Как решить? Как обнаружить?

Проблема N+1 может возникнуть не только при использовании Hibernate, но и других библиотек и фреймворков для доступа к данным.

В общем случае говорят о проблеме N+1 запроса, когда фреймворк выполняет N дополнительных запросов выборки данных, когда можно было обойтись всего одним. Соответственно от размера N зависит влияние проблемы на время ответа нашего приложения. Эту ситуацию нельзя обнаружить с помощью slow query log, ибо сами по себе запросы могут выполняться быстро, но их количество окажется большим или даже огромным.

На такое можно нарваться даже при использовании plain sql (jdbc, JOOQ), когда у нас одна сущность (и соответственно таблица) связана с другой. И вот мы подгрузили одним запросом просто список из первых, а потом пошли и в цикле для каждой подгрузили связанную по одному запросу. "Да как вы это допустили!?". Да просто по запарке кто-то в цикле начал вызывать метод, у которого в глубине где-то делается запрос и привет. Как исправить? Использовать JOIN со связанной таблицей при чтении списка. Тогда понадобиться лишь один запрос.

Теперь к Hibernate. Если на странице документации поискать "N+1", то можно обнаружить несколько упоминаний данной проблемы. Тут опишу самые явные и распространённые.

Например, возьмём стратегию выборки FetchType.EAGER. Она склонна к порождению N+1. А в отношении @ManyToOne по умолчанию используется именно она. Забыли в своём JPQL запросе заиспользовать JOIN FETCH и привет. А если нам и не нужны были связанные сущности, то тогда стоит задать стратегию FetchType.LAZY.

Если уж упомянули FetchType.LAZY, то сразу стоит сказать, что одно её наличие не гарантирует отсутствие проблемы N+1. При выборке списка сущностей, связанные автоматически не подгрузились. А мы потом пошли в цикле по загруженному списку и стали обращаться к полям связанной сущности - и снова здравствуйте. Всё тот же JOIN FETCH нас спасёт и в этой ситуации.

Но JOIN FETCH во многих случаях нас может привести к декартовому произведению, и тогда будет совсем bonjour. Для отношения @OneToMany это можно решить с помощью FetchMode.SUBSELECT - будет 2 запроса, но во втором запросе на получение списка связанных сущностей в условии выборки будет подзапрос на получение идентификаторов родительских сущностей. Т.е. запрос практически повторяется и он может быть тяжеловесным.

Есть вариант лучше - вычитывать связанные сущности пачками. Мы можем добавить аннотацию @BatchSize и указать размер подгружаемой пачки записей в одном запросе.

Ещё варианты:

Чтобы обнаружить проблему N+1, нужно писать тесты с использованием библиотеки db-util от Vlad Mihalcea. Подробнее можно прочитать у него же в блоге.

А вот JOOQ умеет обнаруживать N+1 автоматически, послушать об этом можно в 17-м эпизоде (01:16:36) подкаста Паша+Слава. Подробнее в документации JOOQ.

Углубиться в проблему можно:

к содержанию

Как описать составной ключ при использовании Hibernate?

На всякий случай: составной ключ - первичный ключ, состоящий из двух и более атрибутов. Вообще про ключи есть большая статья на Хабре с ценными комментариями.

Чтобы описать составной ключ при использовании Hibernate, нам необходимо создать под этот ключ отдельный класс с необходимыми полями и добавить ему аннотацию @Embeddable. Кроме того, он должен быть Serializable и иметь реализацию equals и hashcode.

В самой же сущности, для которой мы описываем составной ключ, добавляем поле только что созданного класса ключа и вешаем на него аннотацию @EmbeddedId.

Посмотреть примеры и углубиться в тему можно в статье Vlad Mihalcea или в документации Hibernate.

к содержанию

Как можно отобразить наследование на БД с помощью JPA (Hibernate)?

Есть 4 способа отобразить наследование на БД с помощью JPA (Hibernate):

  • MappedSuperclass - поля родителя содержатся в каждой таблице для каждого дочернего класса. Базовый класс отдельной таблицы не имеет. На базовый класс навешиваем @MappedSuperClass, а вот на дочерние @Entity. Если в таблице потомка поле родителя называется не так, как указано в родительском классе, то его нужно смаппить с помощью аннотации @AttributeOverride в классе этого потомка. Родитель не может участвовать в ассоциации. При полиморфных запросах у нас будут отдельные запросы для каждой таблицы.
  • Single table - вся иерархия классов в одной таблице. Чтобы различать классы, необходимо добавить колонку-дискриминатор. В данной стратегии на родительский @Entity-класс навешивается @Inheritance(strategy = InheritanceType.SINGLE_TABLE) и @DiscriminatorColumn(name = "YOUR_DISCRIMINATOR_COLUMN_NAME") (по умолчанию имя колонки DTYPE и тип VARCHAR). В каждом подклассе указываем @DiscriminatorValue("ThisChildName") со значением, которое будет храниться в колонке-дискриминаторе для данного класса. Если нет возможности добавить колонку, то можно использовать аннотацию @DiscriminatorFormula, в которой указать выражение CASE...WHEN - это не по JPA, фишка Hibernate. Денормализация. Простые запросы к одной таблице. Возможное нарушение целостности - столбцы подклассов могут содержать NULL.
  • Joined table - отдельные таблицы для всех классов иерархии, включая родителя. В каждой таблице только свои поля, а в дочерних добавляется внешний (он же первичный) ключ для связи с родительской таблицей. В @Entity-класс родителя добавляем @Inheritance(strategy = InheritanceType.JOINED). Для полиморфных запросов используются JOIN, а также выражение CASE...WHEN, вычисляющее значение поля _clazz, которое заполняется литералами (0 (родитель), 1, 2 и т.д.) и помогает Hibernate определить какого класса будет экземпляр.
  • Table per class - также как и в MappedSuperclass, имеем отдельные таблицы для каждого подкласса. Базовый класс отдельной таблицы не имеет. По спецификации JPA 2.2 (раздел 2.12) данная стратегия является опциональной, но в Hibernate реализована, поэтому продолжим. В данном случае на базовый класс мы навешиваем @Entity и @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS). Поле первичного ключа (@Id) обязательно для родительского класса. Также аннотация @AttributeOverride в этой стратегии не работает - называйте родительские поля в таблицах сразу единообразно. Полиморфный запрос будет использовать UNION для объединения таблиц. Чтобы различить при создании экземпляров подклассы, Hibernate добавляет поле _clazz в запросы, содержащие литералы (1, 2 и т.д.). А одинаковый набор столбцов для объединения добирается как NULL AS some_field. Родитель может участвовать в ассоциации с другими сущностями.

Почитать ещё по теме с примерами можно:

Кроме того, можно заглянуть в JavaDoc с аннотациями:

Или почитать спецификацию JPA 2.2. Ещё есть книжка с толкованием данного pdf.

к содержанию