Hibernate
Что это
Hibernate - библиотека предназначенная для решения задач объектно-реляционного отображения (ORM), самая
популярная реализация спецификации JPA. Объектно-реляционная модель описывает отношения
между программными объектами и записями в БД.
Слои взаимодействия
- Application
- Spring Data JPA(Repository) - это механизм организации репозиториев, а репозиторий - это абстракция,
лежащая на уровень выше ORM.
- Java Persistence API(JPA) — спецификация API Java EE, предоставляет возможность сохранять в удобном виде
Java-объекты в базе данных. Имеет только интерфейс. Как реализация чаще всего используется Hibernate.
- Hibernate
- JDBC
- Relational Database
Минусы
- Необходимо отслеживать проблему "N + 1"
Проблема "N + 1" запросов возникает, когда при работе с базой данных выполняется один основной запрос для
получения списка объектов (N), а затем для каждого из этих объектов выполняется еще один дополнительный
запрос (+1). Это приводит к множеству SQL-запросов, что сильно снижает производительность.
Решения:
Если вам нужна максимальная производительность, лучше использовать JOIN FETCH или @EntityGraph.
- Fetch Join (JOIN FETCH) - позволяет загружать связанные сущности. Минусы: Может приводить к дублирующим
данным, если есть несколько связанных коллекций.
List<Book> books = entityManager.createQuery(
"SELECT b FROM Book b JOIN FETCH b.author", Book.class)
.getResultList();
- @EntityGraph - позволяет указывать, какие связанные сущности загружать сразу. Минусы: Не работает с
nativeQuery.
@EntityGraph(attributePaths = {"author"})
@Query("SELECT b FROM Book b")
List<Book> findAllWithAuthors();
- @BatchSize - загружать связанные объекты партиями, уменьшая количество SQL-запросов. Минусы: Не даёт
полного контроля над SQL-запросами. Минусы: Не даёт полного контроля над SQL-запросами.
@Entity
public class Book {
@ManyToOne
@BatchSize(size = 10) // Загружает авторов по 10 за раз
private Author author;
}
- @Fetch(FetchMode.SUBSELECT) - позволяет загружать коллекции через один подзапрос вместо множества
маленьких SQL. Минусы: Может быть неэффективным на больших объемах данных.
@Entity
public class Author {
@OneToMany(mappedBy = "author")
@Fetch(FetchMode.SUBSELECT) // Загружает все книги автора одним подзапросом
private List<Book> books;
}
- @NamedEntityGraph - создать предопределённые графы загрузки. Минусы: Нужно явно указывать граф в запросе.
@NamedEntityGraph(name = "Book.withAuthor",
attributeNodes = @NamedAttributeNode("author"))
@Entity
public class Book {
@ManyToOne
private Author author;
}
EntityGraph<?> entityGraph = entityManager.getEntityGraph("Book.withAuthor");
List<Book> books = entityManager.createQuery("SELECT b FROM Book b", Book.class)
.setHint("javax.persistence.fetchgraph", entityGraph)
.getResultList();
Java Persistence Query Language (JPQL)
JPQL - платформенно-независимый объектно-ориентированный язык запросов, являющийся частью спецификации Java
Persistence API (JPA). JPQL используется для написания запросов к сущностям, хранящимся в реляционной базе
данных.
Spring конфигурация application.properties
- spring.jpa.hibernate.ddl-auto=none - чтобы Hibernate не выполнял никаких действий по модификации схемы.
К примеру для работы через Liquibase.
Аннотации для управления
- @Entity(name = "") - указывает что данный класс является сущностью(entity bean). Такой класс должен:
- Иметь конструктор по-умолчанию (пустой конструктор) public или protected.
- Не может быть final и не может содержать final-полей/свойств;
- Должен содержать хотя бы одно @Id-поле.
- Не может быть вложенным, интерфейсом или enum
- @Table(name = "") - указывает с какой именно таблицей необходимо связать (map) данный класс.
Имеет различные аттрибуты для указания имя таблицы, каталог, БД и уникальность столбцов в таблице БД.
- @Id - указывает первичный ключ (Primary Key) данного класса.
- @GeneratedValue - Эта аннотация используется вместе с аннотацией @Id и определяет такие параметры, как
strategy и generator.
- @Column - определяет к какому столбцу в таблице БД относится конкретное поле класса.
Аттрибуты: name, unique, nullable, length.
- @Access - определяет режим доступа к полям. Мы можем использовать @Access на уровне класса, поля
или метода. Мы можем даже смешивать два типа в одном классе сущностей. По умолчанию тип доступа
определяется местом, в котором находится ваш идентификатор(@ Id). Если он будет в поле -
это будет AccessType.FIELD, если он будет в геттере - это AccessType.PROPERTY. Иногда вам может
потребоваться аннотировать не поля, а свойства (например, потому что вы хотите иметь какую-то
произвольную логику в геттере или потому, что вы так предпочитаете). В такой ситуации вы должны
определить геттер и аннотировать его как AccessType.PROPERTY.
- AccessType.FIELD - обычный доступа к полю.
- AccessType.PROPERTY - для доступа к полю через get/set.
Отношения между таблицами
Дополняется CascadeType, FetchType и др.
- @OneToOne - рекомендуется EAGER.
- @OneToMany - рекомендуется LAZY.
- @ManyToOne - рекомендуется EAGER.
- @ManyToMany - рекомендуется LAZY.
CascadeType
- CascadeType.PERSIST - сохранение нового дочернего объекта, вместе с родителем(save()/persist()).
- CascadeType.MERGE - обновление родителя вместе с дочерним.
- CascadeType.REMOVE - удаление родителя вместе с дочерним.
- CascadeType.REFRESH - синхронизация родителя и дочернего с базой.
- CascadeType.DETACH - при работе вне транзакции чтобы "отвязать" объект от сессии.
- CascadeType.ALL - выполнение каскадно сразу все операции.
Советы и подводные камни
- REMOVE может быть опасен: удалит дочерние объекты — даже если вы этого не хотите.
- PERSIST не работает, если дочерний объект уже в базе — используйте MERGE в этом случае.
- CascadeType.ALL удобно, но не всегда безопасно — лучше указывать только нужные каскады.
JPA имеет 2 типа загрузки(FetchType)
- LAZY(by default) - ORM загрузит сущность или коллекцию отложено, при первом обращении к ней из кода.
- EAGER - заставляет ORM загружать связанные сущности и коллекции сразу, вместе с корневой сущностью.
Состояние Entity:
- New
- Managed
- Removed
- Detached
Session methods - методы получения данных
get()
- Немедленно выполняется запрос.
- Если записи нет, вернет null.
- Вернет полностью инициализированный объект(eager load) по этому медленнее чем Load().
- Если вы не уверенны, что запись есть - используйте get().
load()
- Возвращает прокси без немедленного запроса(ленивая загрузка).
- Запрос к базе выполняется только при обращении к полям объекта.
- Если объекта нет вернет ObjectNotFoundException. Вернет proxy объект (lazy load)по быстрее чем Get().
Если вы уверенны что объект есть - используйте Load().
Caches levels
First-level cache(L1 cache)
- Описание: встроенный кеш, работающий на уровне сессии (Session).
- Работает по умолчанию — вы не можете его отключить.
- Все объекты, загруженные в течение одной сессии, сохраняются в этом кеше.
- Если вы повторно запросите тот же объект в одной сессии, Hibernate вернёт его из кеша, а не из базы данных.
Session session = sessionFactory.openSession();
User user1 = session.get(User.class, 1); // первый запрос — из БД
User user2 = session.get(User.class, 1); // второй запрос — из кеша (не из БД)
Очищается при закрытии сессии или при вызове session.clear() / session.evict().
Second-Level Cache
- Описание: кеш, общий для всех сессий (глобальный).
- Не активен по умолчанию, его нужно настраивать.
- Используется между разными Session и даже разными пользователями.
- Поддерживается через сторонние кеш-провайдеры:
1.Ehcache
2.Infinispan
3.Hazelcast
4.Caffeine
Пример настройки:
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
Аннотация в сущности:
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Entity
public class User { ... }
Кеш запросов (Query Cache)
- Дополнительный уровень поверх второго.
- Кеширует результаты HQL/JPQL/Criteria-запросов.
- Требует включённого второго уровня кеша.
Включается отдельно:
<property name="hibernate.cache.use_query_cache">true</property>