При извлечении сущностей из базы данных обычно встает вопрос - как обеспечить загрузку связанных сущностей на нужную глубину?
Например, для браузера Заказов нужно отобразить дату и сумму заказа совместно с названием Покупателя, т.е. загрузить связанный экземпляр Покупателя. А для экрана редактирования Заказа необходимо загрузить еще и коллекцию Пунктов заказа, причем каждый Пункт заказа должен содержать связанный экземпляр Товара для отображения его наименования.
Загрузка по требованию в большинстве случаев не может помочь, так как обработка данных, как правило, происходит не в транзакции, в которой загружаются сущности, а, например, на клиентском уровне в пользовательском интерфейсе. В то же время задание жадной загрузки в аннотациях сущностей недопустимо, так как приводит к постоянному извлечению всего графа связанных сущностей, который может быть очень большим.
Другой похожей проблемой является ограничение набора локальных атрибутов сущностей загружаемого графа: например, некоторая сущность имеет 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 запросом и представлением.
Представление определяется экземпляром класса 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()
.