что такое ленивая загрузка java
Hibernate: ленивая загрузка, наследование и instanceof
Рассмотрим, в качестве примера, следующую ситуацию. У нас имеется класс User с полями, описывающими пользователя. Имеется класс Phone, который является родительским для классов CellPhone и SatellitePhone. В классе User есть поле содержащее список телефонов пользователя. В целях уменьшения нагрузки на БД мы сделали этот список «ленивым». Он будет загружаться только по требованию.
В такой конфигурации при запросе списка телефонов конкретного пользователя мы можем получить как список проинициализированных объектов-телефонов (например, если они уже есть в кэше), так и список proxy-объектов.
В большинстве ситуаций нам не важно с чем именно мы работаем (реальным объектом или его proxy). При запросе какого-либо поля какого-либо объекта — proxy-объект автоматически проинициализируется, и мы получим ожидаемые данные. Но если нам нужно узнать тип объекта, то все идет наперекосяк.
Давайте разберемся почему так происходит. Основная проблема заключается в том, что Hibernate — не экстрасенс и не может знать заранее (не выполнив запросы к БД) какого типа объекты содержатся в списке. В соответствии с этим создает список, содержащий proxy-объекты, унаследованные от Phone.
Когда наша команда в первый раз столкнулась с данной проблемой мы немного изучили данный вопрос и поняли, что придется делать «костыль». Ошибка возникала в сервисном методе где нужно было точно знать с каким из дочерних классов мы имеем дело. Мы прямо перед этой проверкой внедрили другую: если объект является proxy-объектом, то он инициализируется. После чего благополучно забыли эту неприятную историю.
Со временем проект все рос, бизнес-логика усложнялась. И вот настал момент, когда подобных костылей стало уже слишком много (мы поняли, что так дело не пойдет на третьем или четвертом костыле). Причем данная проблема стала возникать не только при запросе у одного объекта ленивого списка других объектов, но и при прямом запросе из базы данных списка объектов. Отказываться от ленивой загрузки очень не хотелось т.к. база у нас и так сильно нагружена. Мы решили больше не перемешивать архитектурные слои приложения и создать что-нибудь более универсальное.
Схема нашего приложения
В данной схеме запросами к БД занимается DAO слой. Он состоит из 1 абстрактного класса JpaDao в котором определены все базовые методы по работе с базой данных. И множества классов — его наследников, каждый из которых в конечном итоге использует методы базового класса. Итак, как мы побороли проблему с прямым запросом списка объектов разных типов с общим родителем? Мы создали в классе JpaDao методы для инициализации одного прокси-объекта и инициализации списка прокси-объектов. При каждом запросе списка объектов из БД этот список проходит инициализацию (Мы сознательно пошли на такой шаг т.к. если мы запрашиваем какой-то список объектов в нашем приложении — то почти всегда он нужен полностью проинициализированным).
С решением первой проблемы все получилось не так гладко. Вышеописанный способ не подходит так как ленивой загрузкой занимается непосредственно Hibernate. И мы пошли на небольшую уступку. Во всех объектах, содержащих ленивые списки разных типов объектов с одним родителем (например, User со списком Phone) мы переопределили геттеры для этих списков. Пока списки не запрашиваются — все в порядке. Объект содержит только прокси-список и не выполняются лишние запросы. При запросе списка происходит его инициализация.
Что такое ленивая загрузка в Hibernate?
Что такое ленивая загрузка в Java? Я не понимаю процесс. Кто-нибудь может помочь мне понять процесс отложенной загрузки?
Скажем, у вас есть родитель, и у этого родителя есть коллекция детей. Hibernate теперь может «лениво загружать» дочерние элементы, что означает, что он фактически не загружает все дочерние элементы при загрузке родительского элемента. Вместо этого он загружает их по запросу. Вы можете запросить это явно или, что гораздо чаще, hibernate будет загружать их автоматически при попытке доступа к дочернему элементу.
Lazy-loading может помочь значительно улучшить производительность, так как часто дети вам не нужны и поэтому они не будут загружаться.
Также остерегайтесь проблемы n + 1. Hibernate не будет загружать все дочерние элементы при доступе к коллекции. Вместо этого он загрузит каждого ребенка в отдельности. При выполнении итерации по коллекции это вызывает запрос для каждого дочернего элемента. Чтобы избежать этого, вы можете обмануть hibernate для загрузки всех дочерних элементов одновременно, например, вызвав parent.getChildren (). Size ().
Это экономит затраты на предварительную загрузку / предварительное заполнение всех сущностей в большом наборе данных заранее, в то время как вам, в конце концов, не нужны все из них.
Объект, который не содержит всех необходимых вам данных, но знает, как их получить.
Таким образом, при загрузке данного объекта идея состоит в том, чтобы не пытаться загружать связанные объекты, которые вы не можете использовать немедленно, чтобы сэкономить связанные с этим затраты производительности. Вместо этого связанный объект (ы) будет загружен только при использовании.
Это не шаблон, специфичный для доступа к данным и Hibernate, но он особенно полезен в таких областях, и Hibernate поддерживает ленивую загрузку ассоциаций «один ко многим» и ассоциаций «один к одному» и «многие к одному». при определенных условиях. Ленивое взаимодействие обсуждается более подробно в Главе 19 Справочной документации по Hibernate 3.0.
Ленивое извлечение решает, загружать ли дочерние объекты при загрузке родительского объекта. Вы должны сделать эту настройку соответствующего файла отображения hibernate родительского класса. Lazy = true (означает не загружать дочерние элементы). По умолчанию отложенная загрузка дочерних объектов имеет значение true.
Это гарантирует, что дочерние объекты не загружаются, если они явно не вызваны в приложении путем вызова getChild() метода для parent. В этом случае hibernate выдает новый вызов базы данных, чтобы загрузить дочерний getChild() объект, когда он фактически вызывается для родительского объекта.
Но в некоторых случаях вам нужно загрузить дочерние объекты, когда родительский загружен. Просто сделайте lazy = false, и hibernate загрузит дочерний элемент при загрузке parent из базы данных.
Пример: если у вас есть СТОЛ? EMPLOYEE отображается на объект Employee и содержит набор объектов Address. Родительский класс: класс сотрудников, дочерний класс: адресный класс
Ленивая инициализация в Spring Boot 2.2
От переводчика: поскольку Spring Framework является одним из основных фреймворков, на которых мы строим CUBA, то новости о новых возможностях Spring не проходят незаметно для нас. Ленивая инициализация — один из способов уменьшить время первой загрузки приложения, что в наш век повсеместного использования микросервисов является важной метрикой. Для тех, кто предпочитает чтению просмотр видео, есть 10-ти минутное выступление Josh Long, посвященное теме статьи.
Недавно анонсированный первый milestone релиз Spring Boot 2.2 добавляет поддержку ленивой инициализации. В этой статье мы рассмотрим новую функциональность и объясним, как ее включить.
Что это значит — быть ленивым?
Spring Framework поддерживает ленивую инициализацию с тех пор, как его исходный код переехал в git одиннадцать лет назад. По умолчанию, когда контекст приложения обновляется, каждый бин создается заново и производится внедрение его зависимостей. В отличие от этого, если бин сконфигурирован для ленивой инициализации, он не будет создан и его зависимости не будут проставлены, пока в этом нет необходимости.
Включение ленивой инициализации
Преимущества ленивой инициализации
Ленивая инициализация может заметно уменьшить время старта вашего приложения, поскольку на этом этапе загружается меньше классов и создается меньше бинов. Например, маленькое веб-приложение которое использует Actuator и Spring Security, обычно стартует 2,5 секунды. А с ленивой инициализацией этот процесс занимает 2 секунды. Точные величины ускорения будут меняться от приложения к приложению, в зависимости от структуры графа зависимостей бинов.
Примечание переводчика: я запускал вот этот пример, прописав в зависимостях Spring Boot 2.2, и время запуска с ленивой инициализацией было 3 секунды, а без нее — 4. Думаю, что на более серьезных приложениях, существенного выигрыша во времени старта за счет использования ленивой инициализации мы не увидим. Upd: по совету alek_sys отключил валидацию и обновление схемы БД и включил ленивую инициализацию JPA для обоих случаев — получилось 2.7 и 3.7 секунд до появления надписи Started WebApplication in. соответственно
А что там насчет DevTools?
Spring Boot DevTools предоставляют заметное ускорение разработки. Вместо перезапуска JVM и приложения каждый раз, когда вы что-то меняете, DevTools делают “горячий перезапуск” приложения в той же самой JVM. Значительное преимущество такого перезапуска в том, что он дает JIT возможность оптимизировать код, который исполняется при старте приложения. После нескольких перезапусков, исходное время в 2,5 секунды уменьшается почти на 80% до 500 мс. С ленивой инициализацией все обстоит ещё лучше. Установка свойства spring.main.lazy-initialization показывает время перезапуска непосредственно в IDE равное 400 мс.
Обратная сторона ленивой инициализации
Как было показано выше, включение ленивой инициализации может достаточно серьезно уменьшить время запуска приложения. И, возможно, у вас будет непреодолимое желание использовать это постоянно или, как минимум, вы будете задаваться вопросом, почему ленивая инициализация не включена по умолчанию. Есть несколько возможных негативных эффектов, которые лучше прояснить сразу.
Тот факт, что классы не загружаются, а бины не создаются до того момента, пока они не потребуются, может маскировать проблемы, которые раньше могли бы быть выявлены уже на этапе запуска приложения. Например, это может быть отсутствие нужного класса, переполнение памяти или ошибка, связанная с неправильной конфигурацией.
В веб-приложениях ленивая конфигурация может увеличить латентность HTTP запросов, которые вызывают инициализацию бинов. Обычно это первый запрос, но могут быть дополнительные нежелательные эффекты, затрагивающие балансировку нагрузки или автоматическое масштабирование.
Эта штука включена?
Когда включать ленивую инициализацию?
Как мы уже видели выше, ленивая инициализация предлагает заметные улучшения во время запуска приложения, но также есть и обратные стороны, так что нужно очень аккуратно использовать эту возможность.
Одна область, где ленивая инициализация может принести дивиденды (почти без накладных расходов) — это процесс разработки приложения. Пока вы пишете приложение, уменьшенное время перезапуска, которое обеспечивается ленивой инициализацией в комбинации с DevTools, может значительно сэкономить вам время.
Где ещё можно получить преимущества от использования ленивой инициализации — так это в интеграционных тестах. Вы, возможно, уже используете «нарезку» тестов для уменьшения времени выполнения, ограничивая количество инициализирующихся бинов в некоторых типах тестов. Ленивая же инициализация предоставляет альтернативную возможность для достижения такого же результата. Если вы не в той должности, чтобы менять структуру приложения для “нарезать” тестов, или для конкретно ваших тестов нет подходящей “нарезки”, то включение ленивой инициализации ограничит количество бинов теми, которые используются только в вашем тесте. Это уменьшит время выполнения теста, особенно если они запускаются в изолированной среде во время разработки.
Как происходит загрузка классов в JVM
Загрузчик классов
загрузка байт-кода из ресурсов и создание экземпляра класса Class
сюда входит поиск запрошенного класса среди загруженных ранее, получение байт-кода для загрузки и проверка его корректности, создание экземпляра класса Class (для работы с ним в runtime), загрузка родительских классов. Если родительские классы и интерфейсы не были загружены, то и рассматриваемый класс считается не загруженным.
связывание (или линковка)
по спецификации этот этап разбивается еще на три стадии:
инициализация полученного объекта
здесь, в отличие от предыдущих пунктов, вроде бы все понятно, что должно происходить. Было бы, конечно, интересно разобраться как именно это происходит.
Типы загрузчиков Java
Bootstrap – базовый загрузчик, также называется Primordial ClassLoader.
загружает стандартные классы JDK из архива rt.jar
Extension ClassLoader – загрузчик расширений.
загружает классы расширений, которые по умолчанию находятся в каталоге jre/lib/ext, но могут быть заданы системным свойством java.ext.dirs
System ClassLoader – системный загрузчик.
загружает классы приложения, определенные в переменной среды окружения CLASSPATH
В Java используется иерархия загрузчиков классов, где корневым, разумеется, является базовый. Далее следует загрузчик расширений, а за ним уже системный. Естественно, каждый загрузчик хранит указатель на родительский для того, чтобы смочь делегировать ему загрузку в том случае, если сам будет не в состоянии этого сделать.
Абстрактный класс ClassLoader
Три принципа загрузки классов
Делегирование
Запрос на загрузку класса передается родительскому загрузчику, и попытка загрузить класс самостоятельно выполняется, только если родительский загрузчик не смог найти и загрузить класс. Такой подход позволяет загружать классы тем загрузчиком, который максимально близко находится к базовому. Так достигается максимальная область видимости классов. Каждый загрузчик ведет учет классов, которые были загружены именно им, помещая их в свой кэш. Множество этих классов и называется областью видимости.
Видимость
Загрузчик видит только «свои» классы и классы «родителя» и понятия не имеет о классах, которые были загружены его «потомком».
Уникальность
Класс может быть загружен только однажды. Механизм делегирования позволяет убедиться, что загрузчик, инициирующий загрузку класса, не перегрузит загруженный ранее в JVM класс.
Таким образом, при написании своего загрузчика разработчик должен руководствоваться этими тремя принципами.
Нетерпеливая/Ленивая Загрузка В Спящем Режиме
Быстрое и практическое знакомство с различными подходами к загрузке данных – ленивыми и нетерпеливыми – в режиме гибернации.
1. введение
При работе с ORM извлечение/загрузка данных может быть разделена на два типа: нетерпеливая и ленивая.
В этой краткой статье мы укажем на различия и покажем, что их можно использовать в режиме гибернации.
2. Зависимости Maven
Чтобы использовать Hibernate, давайте сначала определим основную зависимость в вашем pom.xml :
3. Нетерпеливая и ленивая загрузка
Первое, что мы должны обсудить здесь, – это то, что такое ленивая загрузка и нетерпеливая загрузка:
Давайте посмотрим, как это на самом деле работает, на некоторых примерах:
Пользователь Ленивый Класс :
В следующем разделе мы увидим, как приведенный выше пример реализован в режиме гибернации.
4. Конфигурация Загрузки
В этом разделе мы рассмотрим, как мы можем настроить стратегии выборки в режиме гибернации. Мы будем повторно использовать примеры из предыдущего раздела.
Отложенную загрузку можно просто включить, используя следующий параметр аннотации:
Для использования нетерпеливой выборки используется следующий параметр:
В следующем разделе мы рассмотрим различия между двумя типами выборки.
5. Различия
Как мы уже упоминали, основное различие между двумя типами выборки заключается в моменте, когда данные загружаются в память.
Давайте взглянем на этот пример:
При использовании подхода ленивой инициализации Набор OrderDetail будет инициализирован только в том случае, если он явно вызывается с помощью геттера или какого-либо другого метода, как показано в приведенном выше примере:
Но при активном подходе в Пользователь стремится он будет инициализирован немедленно в первой строке приведенного выше примера:
Идея отключения прокси-серверов или ленивой загрузки считается плохой практикой в режиме гибернации. Это может привести к тому, что большое количество данных будет извлечено из базы данных и сохранено в памяти, независимо от необходимости в этом.
Для проверки вышеуказанной функциональности можно использовать следующий метод:
Теперь важно взглянуть на запросы, которые генерируются в любом случае:
Приведенная выше настройка в fetching.hbm.xml показывает сгенерированные SQL-запросы. Если вы посмотрите на вывод консоли, то сможете увидеть сгенерированные запросы.
Для ленивой загрузки запроса, который создается для загрузки Пользовательских данных:
Однако при быстрой загрузке мы увидели, что соединение выполняется с помощью USER_ORDER:
6. Преимущества и недостатки
6.1. Ленивая загрузка
6.2. Нетерпеливая Загрузка:
7. Ленивая загрузка в спящем режиме
Hibernate применяет подход с отложенной загрузкой к сущностям и ассоциациям, предоставляя прокси-реализацию классов.
Следует также отметить, что когда ассоциация представлена как класс коллекции (в приведенных выше примерах она представлена как Set orderDetailSet ), то создается оболочка и заменяется исходной коллекцией.
8. Заключение
В этой статье мы показали примеры двух основных типов выборки, используемых в режиме гибернации.