Конструкция приложения ленивой нагрузки Hibernate

Я обычно использую Hibernate в сочетании с Spring каркасом и декларативной демаркацией транзакций (например, @Transactional).

Как мы все знаем, hibernate пытается быть неинвазивным и прозрачным, однако это доказывает немного сложнее при использовании lazy-loaded.


Я вижу ряд альтернатив дизайна с различными уровнями прозрачности.

  • Сделать отношения не ленивыми (например, fetchType=FetchType.EAGER)
    • Это vioalites всей идеей ленивой загрузки.
  • Инициализировать коллекции с помощью Hibernate.initialize(proxyObj);
    • Это подразумевает относительно высокую связь с DAO
    • Хотя мы можем определить интерфейс с initialize, другие реализации не гарантируют предоставление какого-либо эквивалента.
  • Добавить поведение транзакций в постоянные объекты Model (используя динамический прокси или @Transactional).
    • Я не пробовал использовать динамический прокси-подход, хотя я никогда не пытался заставить @Transactional работать с персистентными объектами. Вероятно, из-за того, что спящий режим работает с прокси-сервером.
    • Потеря контроля при совершении транзакций
  • Предоставлять ленивый/нелатный API, например loadData() и loadDataWithDeps()
    • Заставляет приложение знать, когда следует использовать эту процедуру, снова плотное соединение
    • Переполнение метода, loadDataWithA(),...., loadDataWithX()
  • Найти поиск зависимостей, например, только при выполнении операций byId()
    • Требуется много не-объектно-ориентированных подпрограмм, например, findZzzById(zid), а затем getYyyIds(zid) вместо z.getY()
    • Может быть полезно получить каждый объект в коллекции один за другим, если между транзакциями возникнут большие накладные расходы на обработку.
  • Сделать частью приложения @Transactional вместо DAO
    • Возможные соображения вложенных транзакций
    • Требуется подпрограммы, адаптированные для управления транзакциями (например, достаточно маленькие)
    • Небольшое программное воздействие, хотя это может привести к крупным транзакциям.
  • Предоставьте DAO динамическим профилям извлечения, например loadData(id, fetchProfile);
    • Приложения должны знать, какой профиль использовать, когда
  • Тип транзакции типа AoP, например, операции перехвата и при необходимости выполнять транзакции
    • Требуется использование байт-кода или использование прокси-сервера
    • Потеря контроля при совершении транзакций
    • Черная магия, как всегда:)

Я пропустил какой-либо вариант?


Каков ваш предпочтительный подход при попытке минимизировать влияние отношений lazy-loaded в дизайне вашего приложения?

(О, и извините за WoT)

Ответ 1

Как мы все знаем, спящий режим пытается быть как неинвазивным и максимально прозрачным

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

Обратите внимание, что Hibernate не может читать мысли, поэтому, если вы знаете, что вам нужен определенный набор зависимостей для конкретной операции, вам нужно как-то выразить свои намерения в Hibernate.

С этой точки зрения решения, выражающие эти намерения явно (а именно, 2, 4 и 7), выглядят разумными и не страдают от отсутствия прозрачности.

Ответ 2

Я не уверен, какую проблему (вызванную ленинностью) вы намекаете, но для меня самая большая боль заключается в том, чтобы избежать потери контекста сеанса в моих собственных кэшах приложений. Типичный случай:

  • объект foo загружается и помещается в карту;
  • другой поток берет этот объект из карты и вызывает foo.getBar() (то, что никогда не вызывалось раньше и лениво оценивается);
  • бум!

Итак, для решения этой проблемы у нас есть ряд правил:

  • завершать сеансы как можно более прозрачным (например, OpenSessionInViewFilter для webapps);
  • имеют общий API для потоков/пулов потоков, где db session bind/unbind выполняется где-то высоко в иерархии (завернуто в try/finally), поэтому подклассам не нужно думать об этом;
  • при передаче объектов между потоками, идентификаторов пропуска вместо самих объектов. Получающий поток может загружать объект, если ему нужно;
  • при кешировании объектов, никогда не кэширует объекты, а их идентификаторы. Имейте абстрактный метод в своем классе DAO или менеджера для загрузки объекта из кэша Hibernate второго уровня, когда вы знаете идентификатор. Стоимость извлечения объектов из кэша Hibernate второго уровня все же намного дешевле, чем переход в DB.

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

Ответ 3

Очень распространенный шаблон - использовать OpenEntityManagerInViewFilter, если вы создаете веб-приложение.

Если вы создаете службу, я бы открыл TX для общедоступного метода службы, а не для DAO, так как очень часто метод требует, чтобы получить или обновить несколько объектов.

Это решит любое "исключение Lazy Load". Если вам нужно что-то более продвинутое для настройки производительности, я думаю, что выборки профилей - это путь.