Гибкий дизайн приложений с отложенной загрузкой - PullRequest
86 голосов
/ 17 февраля 2011

Я склонен использовать Hibernate в сочетании с Spring framework и его возможностями декларативного разграничения транзакций (например, @ Transactional ).

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


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

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

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


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

(О, простите за WoT )

Ответы [ 3 ]

26 голосов
/ 17 февраля 2011

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

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

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

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

7 голосов
/ 17 февраля 2011

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

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

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

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

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

3 голосов
/ 17 февраля 2011

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

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

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

...