fetchtype eager что это

Связанные сущности в Hibernate

Статья является продолжением описания примера использования Sequence в Hibernate, в которой была рассмотрена только одна сущность и представлено описание сессии Session. В данной статье с использованием практического примера рассмотрим вопрос определения связей между сущностями.

Примечание : чтобы не вынуждать Вас обращаться к начальной статье, часть информации здесь будет повторена.

Классы в Java могут не только наследоваться друг от друга, но и включать в себя в качестве полей другие классы или коллекции классов. В столбцах таблиц БД нельзя хранить сложные составные типы и коллекции таких типов (за некоторыми исключениями). Это не позволяет сохранять подобный объект в одну таблицу. Но можно сохранять каждый класс в свою собственную таблицу, определив необходимые связи между ними. C описания связей между объектами и начнем.

Определение связей между сущностями

Для определения связей между сущностями Hibernate использует аннотации @OneToOne, @OneToMany, @ManyToOne, @ManyToMany.

@OneToOne

Для связи один к одному в обоих классах к соответствующим полям добавляется аннотация @OneToOne. Параметр optional говорит JPA, является ли значение в этом поле обязательным или нет. Связанное поле в User объявлено с помощью аннотации @JoinColumn, параметр name которой обозначает поле в БД для создания связи. Для того, чтобы объявить сторону, которая не несет ответственности за отношения, используется атрибут mappedBy в сущности Passport. Он ссылается на имя свойства связи (passport) на стороне владельца.

Связь в БД между таблицами users и passports осуществляется посредством поля passport_id в таблице users.

@OneToMany и @ManyToOne

Аннотации @OneToMany (один-ко-многим) и @ManyToOne (многие-к-одному) рассмотрим на примере гражданина и его места проживания. Гражданин имеет один основной адрес проживания, но по одному адресу могут проживать несколько человек. В следующем листинге представим эти сущности (лишние поля, не связанные с аннотациями, не отображаются) :

Владельцем в этом примере также будет класс Person, который имеет поле address, связанное с соответствующим объектом. Поскольку адрес у гражданина только один, то используется аннотация @ManyToOne. Аннотацией @JoinColumn определяется поле связи в таблице БД. Таким образом, параметры этих аннотаций несут такую же смысловую нагрузку, что и у связи @OneToOne.

А вот у владеемого объекта на этот раз всё иначе. Поскольку по одному адресу может проживать несколько жильцов, то поле tenants представлено коллекцией, которая имеет аннотацию @OneToMany. Параметр mappedBy также указывает на поле в классе владельца. Параметр fetch=FetchType.EAGER говорит о том, что при загрузке владеемого объекта необходимо сразу загрузить и коллекцию владельцев.

Для чтения связанных объектов из БД используются следующие стратегии загрузок (fetch) : EAGER и LAZY. В первом случае объекты коллекции сразу загружаются в память, во втором случае — только при обращении к ним. Оба этих подхода имеют достоинства и недостатки.

В случае FetchType.EAGER в памяти будут находиться все загруженные объекты, даже если нужен только один объект из десятка (сотен/тысяч). При использовании данной стратегии необходимо быть внимательным, поскольку при загрузке какого-нибудь корневого объекта, который связан со всеми остальными объектами и коллекциями, можно случайно попытаться загрузить в память и всю базу.

Согласно стратегии FetchType.LAZY связанные объекты загружаются только по мере необходимости, т.е. при обращении. Но при этом требуется, чтобы соединение с базой (или транзакция) сохранялись. Если быть точно, то требуется, чтобы объект был attached. Поэтому для работы с lazy объектами тратится больше ресурсов на поддержку соединений.

@ManyToMany

Примером ассоциации @ManyToMany (многие-ко-многим) могут быть отношения студентов и ВУЗов. В одном институте может быть много студентов, студент может учиться в нескольких ВУЗах. Рассмотрим с начала таблицы БД :

Для определения связи @ManyToMany в примере потребуется три таблицы : таблица студентов students, таблица ВУЗов university и таблица связей student_university, в которой будут связаны студенты и ВУЗы. Кроме этого в таблице student_university определены внешние ключи (FOREIGN KEY), предупреждающие появление непрошенных записей при отсутствии родительских.

Теперь можно представить описание сущностей :

Список институтов в сущности Student аннотирован с помощью @ManyToMany. Далее следует аннотация @JoinTable, которая определяет таблицу и поля для связи. Параметр name указывает название таблицы (student_university). Параметр joinColumns указывает на поле, которое используется для прямой связи (идентификатор student_id). Параметр inverseJoinColumns указывает на поле, которое используется для обратной связи (идентификатор university_id). Для указания столбцов связи из таблицы используется аннотация @JoinColumn.

Сущность университета University описана «зеркально».

Пример связанных сущностей

Рассмотрим пример использования аннотаций @OneToMane и @ManyToOne при определении связанных сущностей. В качестве первой сущности будет использоваться пользователь User. Второй сущностью будет автомобиль Auto. Пользователь может владеть несколькими автомобилями, поэтому сущность User будет связана с Auto связью @OneToMany (один-ко-многим). Сущность Auto будет связана с сущностью User связью @ManyToOne (многие-к-одному). Начнем с объектов базы данных :

SQL-скрипты создания таблиц пользователей и автомобилей

Записи таблицы пользователей ничего не знают о записях таблицы автомобилей. А записи таблицы Autos связаны с таблицей Users по внешнему ключу (поле user_id). Синтаксис описания внешних ключей в базах данных представлен здесь.

SQL-скрипт создания Sequence

Генератор последовательностей SEQ_USER используем для определении идентификаторов записей сущностей. Как работать с генераторами последовательностей Sequence в SQL подробно представлено здесь.

Проект тестирования связанных сущностей Hibernate

На следующем скриншоте представлена структура проекта hibernate-entities в среде разработки Eclipse. В проекте необходимо определить файл конфигурации hibernate.cfg.xml и классы-сущности (User и Auto). Модуль HibernateExample будет тестировать настройки hibernate и сущностей. Все библиотеки, необходимые для работы с Oracle и hibernate, размещены в поддиректории lib. После включения их в CLASSPATH они отображаются в корне проекта Eclipse.

Примечание : в демонстрационном примере hibernate был использован «файл-маппинг» person.cfg.xml сущности Person. В данном примере вместо «файл-маппингов» будем использовать аннотации Подробная информация об аннотациях JPA представлена здесь.

Конфигурация hibernate

В конфигурационном XML-файле hibernate.cfg.xml определяем сервер БД (драйвер, пул подключений, диалект, кодировку) и параметры подключения (url, login, password), а также дополнительные параметры, которые будут использованы при работе с сервером. В качестве сервера БД выбран Oracle c пулом подключений в одно соединение. В демонстрационном примере в качестве сервера БД использовался MySQL.

Дополнительно определяем истиное значение свойства «show_sql» для отображения в консоли SQL-скриптов, генерируемых библиотекой Hibernate. В заключении в обязательном порядке определяем маппинг сущностей/классов User и Auto, чтобы не вызывать исключений.

Листинг класса пользователя User

Описание сущности/класса User незначительно изменилось. Добавилось поле List autos, определяющее список автомобилей пользователя.

Описание аннотаций @Table, @Id, @Column, @GeneratedValue, @SequenceGenerator сущности User представлено в предыдущей статье. Здесь дополним список описанием аннотации
@OneToMany
Атрибут fetch в аннотации, определяющий стратегию загрузки дочерних объектов, может принимать одно из двух значений перечисления javax.persistence.FetchType :

Атрибут cascade обозначает, какие из методов интерфейса Session будут распространяться каскадно к ассоциированным сущностям. Возможные варианты : CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE. Необходимо правильно настроить CascadeType, чтобы не подгружать из базы данных лишних ассоциированных объектов-сущностей.

Листинг класса автомобиля Auto

При описании поля user используется аннотация @ManyToOne. Аннотация @JoinColumn определяет поле таблицы БД, по которому сущность Auto связана с пользователем User.

Листинг класса тестирования HibernateExample

HibernateExample используется для тестирования связей между сущностями Hibernate. Сначала создается сессия в методе createHibernateSession. При создании session устанавливается соединение с БД Oracle. Если сессия создана успешо, то в методе saveUser создаются два объекта (user1, user2), открывается транзакция и объекты сохраняются в БД. Для сохранения объектов используются методы save класса Session. После этого создаются два объекта типа Auto, у которых полям user присваивается значение первого пользователя. Объекты автомобилей сохраняются в БД и транзакция завершается.

Читайте также:  dead inside мем что значит

После сохранения объектов в БД, пользователь user1 обновляется с использованием метода refresh() объекта сессии. Описание методов Session представлено здесь.

Выполнение примера

При выполнении примера в консоль выводится информация, представленная ниже. Поскольку установлен соответствующий флаг в файле конфигурации hibernate.cfg.xml, то формируемые библиотекой Hibernate SQL-скрипты также отображаются в консоли.

Информация, выведенная Hibernate в консоль, показывает, что сначала формируются SQL-скрипты (запросы к Sequence) для получения идентификаторов объектов пользователя и автомобиля, после этого создаются SQL-скрипты добавления пользователей и автомобилей в БД. И в заключение Hibernate создает SQL-скрипт select с использованием left outer join для обновления объектов.

«Распечатка» описаний пользователей показывет, что первый user имеет автомобили, второй — нет. Как Hibernate с использованием Sequence определяет значения идентификаторов подробно представлено в предыдущей статье.

В продолжении статьи рассмотрен вопрос чтения объектов с фильтрацией и без фильтрации.

Удаление связанных сущностей

Наличие или отсутствие связанной сущности в базе данных определяет способ удаления. Если связанная сущность отсутствует, то можно использовать оператор DELETE в HQL-запросе объекта Query. Но если сущность содержит связанный объект в таблице БД, то при выполнении транзакции удаления с использованием объекта Query будет вызвано соответствующее исключение. Удаление связанных сущностей необходимо выполнять с использованием объекта сессии Session. Подробнее об этом представлено при описании оператора DELETE в HQL-запросе.

Источник

Hibernate: использование lazy initialization в разработке клиент-серверного приложения

При проектировании доменов приложения, разрабатываемого с использованием Hibernate, разработчику необходимо сделать выбор: инициализировать ли свойства домена, соответствующие коллекциям связанных доменов, сразу (FetchType=EAGER) или же делать это только при обращении к ним (FetchType=LAZY). На самом деле в случае, когда предметная область имеет сколь-либо сложную структуру связей между объектами, выбор уже сделан – загружать полбазы ради одного объекта, как это было бы при FetchType=EAGER, мягко говоря, неразумно. Поэтому ленивая инициализация в случае коллекций есть наиболее предпочтительная стратегия инициализации связанных объектов.

Однако, не всё так просто. Ленивая инициализация реализуется за счёт создания прокси-объекта с помощью JDK Dynamic Proxy или библиотеки CGLIB. В обоих случаях проксирование соответствующих get-методов сводится к обращению к сессии Hibernate для получения необходимых данных. Последнее же в свою очередь означает, что обращение к ленивым свойствам объекта может быть осуществлено только при наличии сессии Hibernate. В противном случае, попытка получить свойство объекта приведёт к незабвенному исключению «LazyInitializationException: could not initialize proxy — the owning Session was closed».

Понятно, что иметь открытую сессию под рукой можно далеко не всегда, что доставляет некоторые неудобства. Так, например, для того, что бы использовать домены с ленивой инициализацией в шаблонах MVC-приложения придётся прибегнуть к методике «OpenSessionInView», суть которой сводится к созданию фильтра, обеспечивающего открытие сессии в начале обработки запроса и её закрытие в конце.

Но, что делать, если домены с загруженными данными нужно использовать вне сессии Hibernate? Например, в случае клиент-серверной архитектуры, когда серверу необходимо передать домены клиенту? Разумеется, что об открытие сессии на стороне клиента не может идти и речи, хотя бы потому, что в общем случае о БД ему ничего не известно. Единственным выходом из ситуации, на мой взгляд, будет «депроксирование» объектов доменов и инициализация их необходимыми данными перед передачей от сервера клиенту.

При таком раскладе слой бизнес логики может спокойно работать с прокси-объектами доменов в рамках сессии Hibernate. Роль слоя служб при этом сводится получению необходимых данных от слоя бизнес-логики и компоновки данных для клиента: создание DTO-объектов на базе классов доменов с помощью копирования определённой глубины и детализации.

Вопрос остаётся только в том, как провести это «депроксирование». В принципе это можно сделать с помощью самого Hibernate’a:

public static T initializeAndUnproxy(T entity) <
if (entity == null ) <
throw new
NullPointerException( «Entity passed for initialization is null» );
>

Такой подход инициализирует все коллекции заданного объекта и «достанет» его из прокси. Однако, у него есть целый ряд недостатков: инициализируются все коллекции подряд и депроксируется только родительский объект (как минимум потому, что непонятно, на сколько глубоко нужно спуститься по графу связей объекта при депроксировании).

Решением в данной ситуации может послужить создание небольшого класса-утилиты, который будет выполнять депроксирование домена путём создания нового объекта того же класса и инициализации свойств, соответствующих базовым классам Hibernate’a. Все остальные свойства объекта будут инициализироваться слоем служб по мере необходимости.

Я не берусь, утверждать, что данный подход является оптимальным и/или единственно верным. Если у вас есть предложения, как решить проблему иначе, — буду рад их услышать.

Источник

Проблема с N+1 запросами в JPA и Hibernate

А пока делимся традиционным переводом полезной статьи.

Введение

В этой статье я расскажу, в чем состоит проблема N + 1 запросов при использовании JPA и Hibernate, и как ее лучше всего исправить.

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

Что такое проблема N + 1

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

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

Проблема заключается в выполнении множества дополнительных запросов, которые в сумме выполняются уже существенное время, влияющее на быстродействие.

Рассмотрим следующие таблицы БД: post (посты) и post_comments (комментарии к постам), которые связаны отношением «один-ко-многим»:

Вставим в таблицу post четыре строки:

А в таблицу post_comment четыре дочерние записи:

Проблема N+1 с простым SQL

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

Если вы выберете post_comments с помощью следующего SQL-запроса:

А позже решите получить заголовок (title) связанного поста (post) для каждого комментария (post_comment):

Вы получите проблему N + 1, потому что вместо одного SQL-запроса вы выполнили пять (1 + 4):

Исправить эту проблему с N + 1 запросом очень просто. Все, что нужно сделать, это извлечь все необходимые данные одним SQL-запросом, например, так:

На этот раз выполняется только один SQL-запрос и возвращаются все данные, которые мы хотим использовать в дальнейшем.

Проблема N + 1 с JPA и Hibernate

При использовании JPA и Hibernate есть несколько способов получить проблему N + 1, поэтому очень важно знать, как избежать таких ситуаций.

Рассмотрим следующие классы, которые мапятся на таблицы post и post_comments:

JPA-маппинг выглядят следующим образом:

FetchType.EAGER

Использование явного или неявного FetchType.EAGER для JPA-ассоциаций — плохая идея, потому что будет загружаться гораздо больше данных, чем вам нужно. Более того, стратегия FetchType.EAGER также подвержена проблемам N + 1.

Читайте также:  какой мед полезен для женщин

У вас используется FetchType.EAGER и каждый раз, когда вы забываете указать JOIN FETCH при загрузке сущностей PostComment с помощью JPQL-запроса или Criteria API:

Вы сталкиваетесь с проблемой N + 1:

Обратите внимание на дополнительные запросы SELECT, которые появились, потому что перед возвращением списка сущностей PostComment необходимо извлечь ассоциацию с post .

На этот раз Hibernate выполнит один SQL-запрос:

FetchType.LAZY

Даже если вы явно перейдете на использование FetchType.LAZY для всех ассоциаций, то вы все равно можете столкнуться с проблемой N + 1.

На этот раз ассоциация с post мапится следующим образом:

Теперь, когда вы запросите PostComment :

Hibernate выполнит один SQL-запрос:

Но если позже вы обратитесь к этой lazy-load ассоциации с post :

Вы получите проблему с N + 1 запросом:

Поскольку ассоциация с post загружается лениво, при доступе к этой ассоциации будет выполняться дополнительный SQL-запрос для получения нужных данных.

Опять же, решение заключается в добавлении JOIN FETCH к запросу JPQL :

Кэш второго уровня

Проблема N + 1 также может возникать при использовании кэша второго уровня для обработки коллекций или результатов запроса.

Например, если выполните следующий JPQL-запрос, использующий кэш запросов:

Если PostComment не находится в кэше второго уровня, то будет выполнено N запросов для получения каждого отдельного PostComment :

Источник

Hibernate Proxy — для чего используются и как получить исходный объект

Hibernate использует прокси-объекты для реализации ленивой загрузки (lazy load) связей «к-одному». Их также можно использовать для улучшения производительности некоторых операций записи.

Упоминания прокси-объектов вы могли встречать при отладке или в логах. Имя класса прокси состоит из имени класса сущности и суффикса, который зависит от версии Hibernate и библиотеки для работы с байт-кодом, которую использует Hibernate.

В этой статье рассмотрим, как определить, является ли объект прокси, поговорим о распространенной проблеме при работе с ними, и о том, как инициализировать его поля и получить оригинальный объект (unproxy).

Как Hibernate генерирует прокси

Hibernate генерирует класс для прокси как подкласс вашей сущности. Начиная с Hibernate 5.3 для его генерации используется Byte Buddy. В более старых версиях использовался Javassist и CGLIB.

Сгенерированный прокси перехватывает все вызовы методов и проверяет, был ли инициализирован проксируемый объект. При необходимости перед выполнением перехваченного метода выполняется запрос к базе данных для инициализации сущности. Если это происходит без активной Hibernate Session, то бросается исключение LazyInitializationException.

Как получить прокси-объект

Прежде чем я покажу вам, как инициализировать и депроксировать прокси-объект давайте посмотрим на два наиболее распространенных случая, когда вы встретите прокси.

Проксированные lazy-ассоциации «к-одному»

Используя ленивую загрузку для ассоциаций «к-одному» вы создаете проблему вашему persistence-провайдеру. Теперь он должен придумать, как узнать, что ваш код хочет использовать ассоциацию и получить связанный объект из базы данных. Для ассоциаций «ко-многим» Hibernate решает эту проблему, инициализируя атрибут собственными реализациями коллекций. Но это не работает для ассоциаций «к-одному». Hibernate не требует от ваших сущностей реализации каких-либо интерфейсов, которые потом он мог бы имплементировать. Тогда остается два варианта:

Добавить некоторый код в get-метод

Сгенерировать прокси-класс, являющийся подклассом вашей сущности.

Первый вариант требует изменения байткода. Это тема для другой статьи, подробнее об этом я расскажу в онлайн-тренинге Hibernate Performance Tuning. В этой статье мы сконцентрируемся на генерации прокси.

Получение прокси для инициализации ассоциации

Можно получить прокси для сущности, вызвав метод getReference у EntityManager или Hibernate Session. В результате возвращается объект, который можно использовать для ассоциации «к-одному» при создании или изменении сущности.

Как вы можете видеть из нижеприведенного лога Hibernate, вызов метода getReference не инициирует запрос к базе данных. Hibernate создает экземпляр прокси-объекта и устанавливает значение только для первичного ключа. Выполнение запроса откладывается до вызова геттера или сеттера для любого поля, не являющегося первичным ключом.

Как определить прокси-объект

Часто LazyInitializationException дает вам понять, что вы работаете с прокси. Hibernate бросает это исключение, если вы обращаетесь к геттеру или к любому полю, кроме первичного ключа, неинициализированного прокси-объекта.

Как инициализировать прокси

Самый простой и наиболее часто используемый подход для инициализации прокси-объекта заключается в вызове геттера или сеттера атрибута, не являющегося первичным ключом. Hibernate проверяет, инициализирован ли прокси, и если нет, то выполняет SQL-запрос, который извлекает сущность перед вызовом вашего геттера или сеттера.

Как получить исходную сущность из прокси

Проблема при работе с прокси

Как я объяснял ранее, Hibernate генерирует прокси-объект, который является подклассом вашего класса сущности. Это может стать проблемой, если ваша ассоциация «к-одному» ссылается на суперкласс в иерархии наследования. В этом случае Hibernate генерирует другой подкласс этого суперкласса, и вы не сможете легко привести его к своему подклассу.

Резюме

Hibernate использует сгенерированные прокси-классы для поддержки ленивой загрузки ассоциаций «к-одному», и вы можете использовать их для инициализации ассоциаций к другим сущностям. Как только вы вызываете геттер или сеттер для поля, не являющегося первичным ключом, Hibernate выполняет SQL-запрос для получения связанной сущности.

Если вы хотите инициализировать прокси-объект, вам нужно делать это с активной сессией Hibernate Session. В противном случае Hibernate выбросит исключение LazyInitializationException.

Если у вас есть lazy-ассоциация «к-одному» к суперклассу в иерархии наследования, то вы не сможете привести прокси-объект в какому-либо из ваших подклассов. Сначала вам нужно получить оригинальную сущность через unproxy.

Всех желающих приглашаем на открытый урок «Telegram bot для получения курса валют». На занятии создадим пользовательский интерфейс, для этого мы сделаем Telegram bot-а. Через него конечный пользователь сможет получать курс валют.
>> РЕГИСТРАЦИЯ

Источник

Hibernate. Основные принципы работы с сессиями и транзакциями

В моей первой статье на Хабре я хотел бы поделиться некоторыми соображениями и замечаниями по работе с Hibernate, касающихся сессий и транзакций. Я остановился на некоторых нюансах, которые возникают при начале освоения этой темы. Признаюсь, сам пока Junior-программист, с Hibernate работал не постоянно, поэтому, как всегда, возможны ошибки, коль заметите оные, буду благодарен за поправки.

Библиотека Hibernate является самой популярной ORM-билиотекой и реализацией Java Persistence API. Часто используется как ORM-провайдер в обычных Java-приложениях, контейнерах сервлетов, в частности, в сервере приложений JBoss (и его потомке WildFly).

1). Объекты-сущности (Entity Objects)

Рассмотрим две сущности — пользователя и его задачи:

Теперь приведём классы-сущности для этих таблиц:

Об аннотациях JPA можно прочитать здесь.

2). Интерфейс Session

The main runtime interface between a Java application and Hibernate. This is the central API class abstracting the notion of a persistence service.
The lifecycle of a Session is bounded by the beginning and end of a logical transaction. (Long transactions might span several database transactions.)
The main function of the Session is to offer create, read and delete operations for instances of mapped entity classes.

Интерфейс org.hibernate.Session является мостом между приложением и Hibernate. С помощью сессий выполняются все CRUD-операции с объектами-сущностями. Объект типа Session получают из экземпляра типа org.hibernate.SessionFactory, который должен присутствовать в приложении в виде singleton.

Читайте также:  что делает социальный работник по уходу за пожилыми людьми
3). Состояния объектов

Объект-сущность может находиться в одном из 3-х состояний (статусов):

А теперь обратим внимание на аннотации @OneToMany и @ManyToOne в классах-сущностях. Параметр fetch в @OneToMany обозначает, когда загружать дочерние объекты. Может иметь одно из двух значений, указанных в перечислении javax.persistence.FetchType:

FetchType.EAGER — загружать коллекцию дочерних объектов сразу же, при загрузке родительских объектов.
FetchType.LAZY — загружать коллекцию дочерних объектов при первом обращении к ней (вызове get) — так называемая отложенная загрузка.

Параметр cascade обозначает, какие из методов интерфейса Session будут распространяться каскадно к ассоциированным сущностям. Например, в классе-сущности User для коллекции tasks укажем:

Тогда при выполнении session.persist(user) или session.merge(user) операции persist или merge будут применены ко всем объектам из tasks. Аналогично для остальных операций из перечисления javax.persistence.CascadeType. CascadeType.ALL применяет все операции из перечисления. Необходимо правильно настроить CascadeType, дабы не подгружать из базы кучу лишних ассоциированных объектов-сущностей.

4). Извлечение объектов из БД

Приведём простой пример:

Вместо метода session.get() можно использовать session.load(). Метод session.load() возвращает так называемый proxy-object. Proxy-object — это объект-посредник, через который мы можем взаимодействовать с реальным объектом в БД. Он расширяет функционал объекта-сущности. Взаимодействие с proxy-object полностью аналогично взаимодействию с объектом-сущностью. Proxy-object отличается от объекта-сущности тем, что при создании proxy-object не выполняется ни одного запроса к БД, т. е. Hibernate просто верит нам, что объект с данным Id существует в БД. Однако первый вызванный get или set у proxy-object сразу инициирует запрос select, и если объекта с данным Id нет в базе, то мы получим ObjectNotFoundException. Основное предназначение proxy-object — реализация отложенной загрузки.

Вызов user.getTasks() инициирует загрузку задач юзера из БД, так как в классе User для tasks установлен FetchType.LAZY.

LazyInitializationException

С параметром FetchType.LAZY нужно быть аккуратнее. Иногда при загрузке ассоциированных сущностей мы можем поймать исключение LazyInitializationException. В вышеуказанном коде во время вызова user.getTasks() user должен быть либо в статусе persistent, либо proxy.

Также LazyInitializationException может вызвать небольшое изменение в нашем коде:

Здесь теоретически всё верно. Но при попытке обращения к tasksList мы МОЖЕМ получить LazyInitializationException. Но в дебагере данный код отрабатывает верно. Почему? Потому, что user.getTasks() только возвращает ссылку на коллекцию, но не ждёт её загрузки. Не подождав, пока загрузятся данные, мы закрыли сессию. Выход — выполнять в транзакции, т. е.:

Выборка с условиями

А теперь приведём несколько простых примеров выборки данных с условиями. Для этого в Hibernate используются объекты типа org.hibernate.Criteria:

Здесь понятно, что мы выполняем select * from user where login=’login’. В метод add мы передаём объект типа Criterion, представляющий определённый критерий выборки. Класс org.hibernate.criterion.Restrictions предоставляет множество различных видов критериев. Параметр «login» обозначает название свойства класса-сущности, а не поля в таблице БД.
Приведём ещё пару примеров:

Здесь мы выбираем по содержимому свойства name класса-сущности Task. MatchMode.ANYWHERE означает, что нужно искать подстроку name в любом месте свойства «name».

б).
А здесь мы получаем 50 строк, начиная с 20-го номера в таблице.

5). Сохранение объектов

Давайте разберём несколько способов сохранения объекта-сущности в базу данных.

а). Создаём transient-object и сохраняем в базу:

Отметим несколько нюансов. Во-первых, сохранение в БД можно производить только в рамках транзакции. Вызов session.openTransaction() открывает для данной сессии новую транзакцию, а session.getTransaction().commit() её выполняет. Во-вторых, в метод task.setUser(user) мы передаём user в статусе detached. Можно передать и в статусе persistent.

Данный код выполнит (не считая получения user) 2 запроса — select nextval(‘task_task_id_seq’) и insert into task.
Вместо saveOrUpdate() можно выполнить save(), persist(), merge() — будет также 2 запроса. Вызов session.flush() применяет все изменения к БД, но, если честно, этот вызов здесь бесполезен, так как ничего не сохраняется в БД до commit(), который сам вызовет flush().

Помним, что если мы внутри транзакции что-то изменим в загруженном из БД объекте статуса persistent или proxy-object, то выполнится запрос update. Если task должен ссылаться на нового user, то делаем так:

Внимание: в классе Task для поля user должен быть установлен CascadeType.PERSIST, CascadeType.MERGE или CascadeType.ALL.

Если мы имеем на руках userId существующего в БД юзера, то нам не обязательно загружать объект User из БД, делая лишний select. Так как мы не можем присвоить ID юзера непосредственно свойству класса Task, нам нужно создать объект класса User с единственно заполненными userId. Естественно, это не может быть transient-object, поэтому здесь следует воспользоваться известным нам proxy-объектом.

б). Добавляем объект в коллекцию дочерних объектов:

В User для свойства tasks должен стоять CascadeType.ALL. Если стоит CascadeType.MERGE, то после user.getTasks().add(task) выполнить session.merge(user). Данный код выполнит 3 запроса — select * from user, select nextval(‘task_task_id_seq’) и insert into task

6). Удаление объектов

а). Можно удалить, создав transient-object:

Данный код удалит только task. Однако, если task — объект типа proxy, persistent или detached и в классе Task для поля user действует CascadeType.REMOVE, то из базы удалится также ассоциированный user. Если удалять юзера не нужно, выполнить что? Правильно, task.setUser(null)

б). Можно удалить и таким способом:

Данный код просто удаляет связь между task и user. Здесь мы применили новомодное лямбда-выражение. Объект task удалится из БД при одном условии — если изменить кое-что в классе-сущности User:

Параметр orphanRemoval = true указывает, что все объекты Task, которые не имеют ссылки на User, должны быть удалены из БД.

7). Декларативное управление транзакциями

Для декларативного управления транзакциями мы будем использовать Spring Framework. Управление транзакциями осуществляется через менеджер транзакций. Вместо вызовов session.openTransaction() и session.commit() используется аннотация @Transactional. В конфигурации приложения должно присутствовать следующее:

Здесь мы определили бин transactionManager, к которому привязан бин sessionFactory. Класс HibernateTransactionManager является реализацией общего интерфейса org.springframework.transaction.PlatformTransactionManager для SessionFactory библиотеки Hibernate. annotation-driven указывает менеджеру транзакций обрабатывать аннотацию @Transactional.

— Болтовня ничего не стоит. Покажите мне код. (Linus Torvalds)

Аннотация @Transactional указывает, что метод должен выполняться в транзакции. Менеджер транзакций открывает новую транзакцию и создаёт для неё экземпляр Session, который доступен через sessionFactory.getCurrentSession(). Все методы, которые вызываются в методе с данной аннотацией, также имеют доступ к этой транзакции, потому что экземпляр Session является переменной потока (ThreadLocal). Вызов sessionFactory.openSession() откроет совсем другую сессию, которая не связана с транзакцией.

Параметр rollbackFor указывает исключения, при выбросе которых должен быть произведён откат транзакции. Есть обратный параметр — noRollbackFor, указывающий, что все исключения, кроме перечисленных, приводят к откату транзакции.

Параметр propagation самый интересный. Он указывает принцип распространения транзакции. Может принимать любое значение из перечисления org.springframework.transaction.annotation.Propagation. Приведём пример:

Метод UserDao.getUserByLogin() также может быть помечен аннотацией @Transactional. И здесь параметр propagation определит поведение метода UserDao.getUserByLogin() относительно транзакции метода saveTask():

Ну что ж, подведём итоги

В моей статье я осветил самые основные принципы работы с сессиями и транзакциями в Hibernate. Надеюсь, что начинающим Java-программистам статья будет полезна при преодолении первого порога в изучении суперклассной (не для всех, возможно) библиотеки Hibernate. Желаю всем успехов в нашей сложной и интересной программерской деятельности!

Источник

Сказочный портал