Pull to refresh

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

Java *
При проектировании доменов приложения, разрабатываемого с использованием 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? Например, в случае клиент-серверной архитектуры, когда серверу необходимо передать домены клиенту? Разумеется, что об открытие сессии на стороне клиента не может идти и речи, хотя бы потому, что в общем случае о БД ему ничего не известно. Единственным выходом из ситуации, на мой взгляд, будет «депроксирование» объектов доменов и инициализация их необходимыми данными перед передачей от сервера клиенту.

Представим, что серверная часть приложения состоит из 3-х слоёв:
  • слой служб (объекты, с которыми работает клиент);
  • слой бизнес-логики (объекты, реализующие бизнес-логику приложения);
  • слой персистенции (Hibernate, домены).

Сами классы доменов доступны как серверу, так и клиенту (что, в общем, логично).

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

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

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

  Hibernate.initialize(entity);
  if (entity instanceof HibernateProxy) {
    entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer()
        .getImplementation();
  }
  return entity;
}

* This source code was highlighted with Source Code Highlighter.


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

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

Я не берусь, утверждать, что данный подход является оптимальным и/или единственно верным. Если у вас есть предложения, как решить проблему иначе, — буду рад их услышать.
Tags:
Hubs:
Total votes 24: ↑19 and ↓5 +14
Views 47K
Comments 46
Comments Comments 46

Posts