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, который функционально повторяет стандартную реализацию загрузчика, однако вы можете использовать собственный сервис или же выполнить пост-обработку загруженных сущностей.

Загрузчики также можно создавать и настраивать программно, к примеру:

@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-компонентах:

Значение введено только в nameFilterField
select e from demo_Customer e where e.name like :name
Значение введено только в statusFilterField
select e from demo_Customer e where e.status = :status
Значение введено и в nameFilterField и в statusFilterField
select e from demo_Customer e where (e.name like :name) and (e.status = :status)