4.2.3. Представления

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

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

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

Другой похожей проблемой является ограничение набора локальных атрибутов сущностей загружаемого графа: например, некоторая сущность имеет 50 атрибутов, в том числе BLOB, а в экране отображается только 10 атрибутов. Зачем загружать из БД, затем сериализовать и передавать клиенту 40 атрибутов, которые ему в данный момент не нужны?

Механизм представлений (views) решает эти проблемы, обеспечивая извлечение из базы данных и передачу клиенту графов сущностей, ограниченных в глубину и по атрибутам. Представление является описателем графа объектов, который требуется в некотором экране UI или другом процессе обработки данных.

Обработка представлений производится следующим образом:

  • Все связи в модели данных объявляются с признаком загрузки по требованию (fetch = FetchType.LAZY, см. Раздел 4.2.1.2, «Аннотации сущностей»).

  • В процессе загрузки данных через DataManager клиентский код помимо JPQL запроса указывает нужное представление.

  • На основе представления формируется так называемый Fetch Plan - особенность лежащего в основе слоя ORM фреймворка Apache OpenJPA. Fetch Plan влияет на формирование SQL запроса к базе данных: как на список возвращаемых полей, так и на соединения с другими таблицами, содержащими связанные сущности.

  • В представлении некоторые ссылочные атрибуты могут быть объявлены как lazy (см. ниже). Lazy-атрибуты не включаются в Fetch Plan, а загружаются отдельными SQL запросами (иногда это полезно для упрощения основного SQL запроса). Для этого механизм обработки представлений просто обращается к соответствующим методам чтения атрибутов.

  • В результате на момент завершения транзакции, загружающей данные, в памяти Middleware содержится граф объектов, заданный JPQL запросом и представлением.

Рисунок 4.7. Классы представления

Классы представления

Представление определяется экземпляром класса View, в котором:

  • entityClass - класс сущности, для которого определено представление. Другими словами, "корень" дерева загружаемых сущностей.

  • name - имя представления. Должно быть либо null, либо уникальным в пределах данной сущности.

  • properties - коллекция экземпляров класса ViewProperty, соответствующих загружаемым атрибутам сущности.

  • includeSystemProperties - признак включения системных атрибутов (входящих в состав базовых интерфейсов персистентных сущностей BaseEntity и Updatable). Системные атрибуты не перечисляются в properties явно, а учитываются механизмом обработки представлений в зависимости от того, какие интерфейсы реализует данная сущность.

Класс ViewProperty имеет следующие свойства:

  • name - имя атрибута сущности

  • view - для ссылочных атрибутов задает представление, с которым необходимо загружать связанную сущность

  • lazy - для ссылочных атрибутов признак того, что данный атрибут нужно не включать в Fetch Plan, а загружать отдельным SQL запросом, инициированным обращением к атрибуту. Следует иметь в виду, что при использовании DataManager и источников данных атрибут в любом случае будет загружен, данный признак влияет только на способ загрузки. Если же представление с lazy атрибутами используется на уровне ORM, то после загрузки экземпляров их обязательно нужно передать в метод EntityManager.fetch() до окончания транзакции, иначе lazy атрибуты загружены не будут.

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

  • id - идентификатор сущности

  • version - для оптимистично блокируемых сущностей, реализующих Versioned

  • deleteTs, deletedBy - для сущностей, реализующих SoftDelete

Незагруженные атрибуты имеют значение null. По умолчанию попытка установки значения незагруженного атрибута (вызов setter) для Detached сущности вызывает исключение. Это поведение можно изменить с помощью свойства приложения cuba.allowSetNotLoadedAttributes . Если данное свойство установлено в true, то вызов setter не приведет к исключению, но значение все равно сохранено не будет.

Следует иметь в виду, что незагруженные ссылочные атрибуты Detached сущности, соответствующие внешним ключам (т.е. ManyToOne, OneToOne), можно установить в новое ненулевое значение в любом случае, и изменения будут сохранены при последующем merge().