3.5.3.2. Загрузчики данных
Загрузчики, или loaders, предназначены для загрузки данных со среднего слоя в контейнеры данных.
Интерфейсы загрузчиков немного отличаются в зависимости от типа контейнера, с которым они работают:
-
InstanceLoader
загружает единственный экземпляр сущности в контейнерInstanceContainer
по идентификатору сущности или с помощью JPQL-запроса. -
CollectionLoader
загружает коллекцию сущностей вCollectionContainer
с помощью JPQL-запроса. Для этого загрузчика можно настроить пагинацию, сортировку и другие дополнительные параметры. -
KeyValueCollectionLoader
загружает коллекцию экземпляровKeyValueEntity
в контейнерKeyValueCollectionContainer
. Кроме параметров, доступных дляCollectionLoader
, вы также можете указать имя хранилища данных.
В XML-дескрипторах экрана загрузчики объявляются с помощью элемента <loader>
, тип загрузчика будет определяться типом контейнера, в который он вложен.
Использование загрузчиков необязательно, так как вы можете загружать данные с помощью DataManager
или собственного сервиса и самостоятельно добавлять их в контейнеры, однако загрузчики облегчают этот процесс для экранов, описываемых декларативно, особенно в случае компонента Filter. Обычно загрузчик коллекций получает запрос JPQL из XML-дескриптора экрана, а параметры запроса - из компонента Filter
, затем создаёт объект LoadContext
и вызывает DataManager
для загрузки сущностей. В итоге, XML-дескриптор выглядит подобным образом:
<data>
<collection id="customersDc" class="com.company.sample.entity.Customer" view="_local">
<loader id="customersDl">
<query>
select e from sample_Customer e
</query>
</loader>
</collection>
</data>
<layout>
<filter id="filter" applyTo="customersTable" dataLoader="customersDl">
<properties include=".*"/>
</filter>
<!-- ... -->
</layout>
Дополнительные параметры для XML-элемента loader
можно указать с помощью атрибутов cacheable
, softDeletion
и т.д.
В экране редактора сущности XML-элемент loader
обычно пуст, так как для загрузки единственного экземпляра сущности требуется её идентификатор, который устанавливается программно классом StandardEditor
:
<data>
<instance id="customerDc" class="com.company.sample.entity.Customer" view="_local">
<loader/>
</instance>
</data>
Загрузчики могут делегировать непосредственно загрузку данных отдельной функции, переданной с помощью метода setLoadDelegate()
либо декларативно с помощью аннотации @Install
в контроллере экрана, например:
@Inject
private DataManager dataManager;
@Install(to = "customersDl", target = Target.DATA_LOADER)
protected List<Customer> customersDlLoadDelegate(LoadContext<Customer> loadContext) {
return dataManager.loadList(loadContext);
}
В данном примере метод customersDlLoadDelegate()
используется загрузчиком customersDl
для получения списка экземпляров сущности Customer
. Метод принимает LoadContext
, который будет создан загрузчиком на основе его параметров: запрос, фильтр (при наличии) и т.д. В этом примере загрузка осуществляется через интерфейс DataManager
, который функционально повторяет стандартную реализацию загрузчика, однако вы можете использовать собственный сервис или же выполнить пост-обработку загруженных сущностей.
Загрузчики посылают события PreLoadEvent
и PostLoadEvent
, которые можно использовать для выполнения некоторой логики до или после загрузки:
@Subscribe(id = "customersDl", target = Target.DATA_LOADER)
private void onCustomersDlPreLoad(CollectionLoader.PreLoadEvent<Customer> event) {
// do something before loading
}
@Subscribe(id = "customersDl", target = Target.DATA_LOADER)
private void onCustomersDlPostLoad(CollectionLoader.PostLoadEvent<Customer> event) {
// do something after loading
}
Загрузчики также можно создавать и настраивать программно, к примеру:
@Inject
private DataComponents dataComponents;
private void createCustomerLoader(CollectionContainer<Customer> container) {
CollectionLoader<Customer> loader = dataComponents.createCollectionLoader();
loader.setQuery("select e from sample_Customer e");
loader.setContainer(container);
loader.setDataContext(getScreenData().getDataContext());
}
Если для загрузчика установлен DataContext (как всегда бывает в случае, если загрузчик задан в XML-дескрипторе), все загруженные сущности будут автоматически помещены в data context.
- Условия запросов
-
Иногда необходимо изменить запрос загрузчика данных во время выполнения программы для того, чтобы отфильтровать загружаемые данные на уровне БД. Простейший способ фильтрации в зависимости от параметров, вводимых пользователем - это подключить к загрузчику UI-компонент Filter.
Вместо использования универсального фильтра, или в дополнение к нему, для запроса в загрузчике можно задать набор условий. Условие представляет собой набор фрагментов запросов с параметрами. Эти фрагменты будут добавлены в результирующий запрос, только если все параметры, используемые во фрагментах, заданы для запроса. Условия обрабатываются на уровне хранилищ данных, поэтому они могут содержать фрагменты различных языков запросов, поддерживаемых хранилищами. Фреймворк предоставляет возможность описывать условия на языке JPQL.
Рассмотрим создание условий для фильтрации сущности
Customer
по двум ее атрибутам: строковомуname
и булевскомуstatus
.Условия запроса для загрузчика могут быть заданы либо декларативно в XML-элементе
<condition>
, либо программно методомsetCondition()
. Ниже приведен пример описания условий в XML:<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd" xmlns:c="http://schemas.haulmont.com/cuba/screen/jpql_condition.xsd" (1) caption="Customers browser" focusComponent="customersTable"> <data> <collection id="customersDc" class="com.company.demo.entity.Customer" view="_local"> <loader id="customersDl"> <query><![CDATA[select e from demo_Customer e]]> <condition> (2) <and> (3) <c:jpql> (4) <c:where>e.name like :name</c:where> </c:jpql> <c:jpql> <c:where>e.status = :status</c:where> </c:jpql> </and> </condition> </query> </loader> </collection> </data>
1 - добавьте namespace для JPQL-условий 2 - добавьте элемент condition
внутриquery
3 - если необходимо задать более одного условия, добавьте элемент and
илиor
4 - задайте JPQL-условие с опциональным элементом join
и обязательнымwhere
Предположим, что в экране имеется два UI-компонента для ввода параметров условий: текстовое поле
nameFilterField
и флажокstatusFilterField
. Для того, чтобы обновить данные, когда пользователь изменяет значения в этих компонентах, добавим следующие подписки на события в контроллере экрана:@Inject private CollectionLoader<Customer> customersDl; @Subscribe("nameFilterField") private void onNameFilterFieldValueChange(HasValue.ValueChangeEvent<String> event) { if (event.getValue() != null) { customersDl.setParameter("name", "(?i)%" + event.getValue() + "%"); (1) } else { customersDl.removeParameter("name"); } customersDl.load(); } @Subscribe("statusFilterField") private void onStatusFilterFieldValueChange(HasValue.ValueChangeEvent<Boolean> event) { if (event.getValue()) { customersDl.setParameter("status", true); } else { customersDl.removeParameter("status"); } customersDl.load(); }
1 - обратите внимание, что здесь используется Поиск подстроки без учета регистра обеспечиваемый ORM Как было упомянуто выше, условие включается в запрос только когда его параметры установлены. Поэтому результирующий запрос, выполняемый БД, будет зависеть от того, что введено в UI-компонентах:
Значение введено только в nameFilterFieldselect e from demo_Customer e where e.name like :name
Значение введено только в statusFilterFieldselect e from demo_Customer e where e.status = :status
Значение введено и в nameFilterField и в statusFilterFieldselect e from demo_Customer e where (e.name like :name) and (e.status = :status)