Загрузчики данных
Загрузчики, или loaders, предназначены для загрузки данных в контейнеры.
Интерфейсы загрузчиков немного отличаются в зависимости от типа контейнера, с которым они работают:
-
InstanceLoader
загружает единственный экземпляр сущности в контейнерInstanceContainer
по идентификатору сущности или с помощью JPQL-запроса. -
CollectionLoader
загружает коллекцию сущностей вCollectionContainer
с помощью JPQL-запроса. Для этого загрузчика можно настроить пейджинг, сортировку и другие дополнительные параметры. -
KeyValueCollectionLoader
загружает коллекцию экземпляровKeyValueEntity
в контейнерKeyValueCollectionContainer
. Кроме параметров, доступных дляCollectionLoader
, вы также можете указать имя хранилища данных.
В XML-дескрипторах экрана загрузчики объявляются с помощью элемента <loader>
, и тип загрузчика будет определяться типом контейнера, в который он вложен.
Использование загрузчиков необязательно, так как вы можете загружать данные с помощью DataManager
или собственного сервиса и самостоятельно добавлять их в контейнеры, однако загрузчики облегчают этот процесс для экранов, описываемых декларативно, особенно в случае компонента Filter.
Обычно загрузчик коллекций получает запрос JPQL из XML-дескриптора экрана, а параметры запроса – из компонента Filter
, затем создает объект LoadContext
и вызывает DataManager
для загрузки сущностей. В итоге XML-дескриптор выглядит подобным образом:
<data readOnly="true">
<collection id="customersDc"
class="ui.ex1.entity.Customer">
<fetchPlan extends="_base"/>
<loader id="customersDl" >
<query>
<![CDATA[select e from uiex1_Customer e]]>
</query>
</loader>
</collection>
</data>
<layout expand="customersTable" spacing="true">
<filter id="filter"
dataLoader="customersDl">
<properties include=".*"/>
</filter>
<!-- ... -->
</layout>
В экране редактора сущности XML-элемент loader
обычно пуст, так как для загрузки единственного экземпляра сущности требуется ее идентификатор, который устанавливается программно классом StandardEditor
:
<data>
<instance id="customerDc"
class="ui.ex1.entity.Customer">
<fetchPlan extends="_base"/>
<loader/>
</instance>
</data>
Загрузчики также можно создавать и настраивать программно, например:
@Autowired
private DataComponents dataComponents;
private CollectionLoader<Customer> customersDl;
private void createCustomerLoader(CollectionContainer<Customer> container) {
customersDl = dataComponents.createCollectionLoader();
customersDl.setQuery("select e from uiex1_Customer e");
customersDl.setContainer(container);
customersDl.setDataContext(getScreenData().getDataContext());
}
Если для загрузчика установлен DataContext (как всегда бывает в случае, если загрузчик задан в XML-дескрипторе), все загруженные сущности будут автоматически помещены в data context.
События и слушатели
В данном разделе описываются события жизненного цикла загрузчиков данных, на которые можно подписаться в контроллерах экранов.
Чтобы сгенерировать заглушку слушателя в Jmix Studio, выберите элемент контейнера данных в XML-дескрипторе экрана или на панели Component Hierarchy и используйте вкладку Handlers панели Component Inspector. В качестве альтернативы вы можете воспользоваться кнопкой Generate Handler на верхней панели контроллера экрана. |
loadDelegate
Загрузчики могут делегировать фактическую загрузку методу контроллера экрана, где можно вызвать настраиваемую службу вместо используемого по умолчанию DataManager
. Например:
@Autowired
private CustomerService customerService;
@Install(to = "customersDl", target = Target.DATA_LOADER) (1)
protected List<Customer> customersDlLoadDelegate(LoadContext<Customer> loadContext) { (2)
LoadContext.Query query = loadContext.getQuery();
return customerService.loadCustomers( (3)
query.getCondition(),
query.getSort(),
query.getFirstResult(),
query.getMaxResults()
);
}
1 | Метод customersDlLoadDelegate() используется загрузчиком customersDl для получения списка экземпляров сущности Customer . |
2 | Метод принимает LoadContext , который будет создан загрузчиком на основе его параметров: запрос, фильтр (при наличии) и т.д. |
3 | Загрузка осуществляется методом CustomerService.loadCustomers() , который принимает условия фильтрации, сортировку и пейджинг, установленные загрузчику визуальными компонентами экрана. |
Помимо вызова собственных сервисов, в делегате можно выполнить пост-обработку загруженных сущностей.
Если вы объявляете собственную загрузку данных с помощью делегата и отображаете загруженные данные в таблице с помощью компонента разбивки на страницы (Pagination или SimplePagination), то вам также может потребоваться определить собственную логику для подсчета общего количества строк. Обратите внимание на слушателя TotalCountDelegate компонента разбивки на страницы, связанного с таблицей.
PreLoadEvent
Это событие отправляется перед загрузкой сущностей.
@Subscribe(id = "customersDl", target = Target.DATA_LOADER)
public void onCustomersDlPreLoad(CollectionLoader.PreLoadEvent<Customer> event) {
// some actions before loading
}
Загрузку можно предотвратить, используя метод события preventLoad()
.
PostLoadEvent
Это событие отправляется после того, как сущности успешно загружены, помещены в DataContext
и установлены в контейнер.
@Subscribe(id = "customersDl", target = Target.DATA_LOADER)
public void onCustomersDlPostLoad(CollectionLoader.PostLoadEvent<Customer> event) {
// some actions after loading
}
Условия запросов
Иногда необходимо изменить запрос загрузчика данных во время выполнения программы для того, чтобы отфильтровать загружаемые данные на уровне БД. Простейший способ фильтрации в зависимости от параметров, вводимых пользователем – это подключить к загрузчику визуальный компонент Filter.
Вместо использования универсального фильтра, или в дополнение к нему, для запроса в загрузчике можно задать набор условий. Условие представляет собой набор фрагментов запросов с параметрами. Эти фрагменты будут добавлены в результирующий запрос, только если все параметры, используемые во фрагментах, заданы для запроса. Условия обрабатываются на уровне хранилищ данных, поэтому они могут содержать фрагменты различных языков запросов, поддерживаемых хранилищами. Фреймворк предоставляет возможность описывать условия на языке JPQL.
Рассмотрим создание условий для фильтрации сущности Person
по ее атрибуту name
.
Условия запроса для загрузчика могут быть заданы либо декларативно в XML-элементе <condition>
, либо программно методом setCondition()
. Ниже приведен пример описания условий в XML:
<window xmlns="http://jmix.io/schema/ui/window"
xmlns:c="http://jmix.io/schema/ui/jpql-condition">(1)
<data readOnly="true">
<collection id="personsDc"
class="ui.ex1.entity.Person">
<fetchPlan extends="_base"/>
<loader id="personsDl">
<query>
<![CDATA[select e from uiex1_Person 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
. Для того чтобы обновить данные, когда пользователь изменяет значения в этих компонентах, добавим следующие подписки на события в контроллере экрана:
@Autowired
private CollectionLoader<Person> personsDl;
@Subscribe("nameFilterField")
public void onNameFilterFieldValueChange1(HasValue.ValueChangeEvent event) {
if (event.getValue() != null) {
personsDl.setParameter("name", "(?i)%" + event.getValue() + "%");
} else {
personsDl.removeParameter("name");
}
personsDl.load();
}
@Subscribe("statusFilterField")
public void onStatusFilterFieldValueChange(HasValue.ValueChangeEvent<Boolean> event) {
if (event.getValue()) {
personsDl.setParameter("status", true);
} else {
personsDl.removeParameter("status");
}
personsDl.load();
}
Как было упомянуто выше, условие включается в запрос только когда его параметры установлены. Поэтому результирующий запрос, выполняемый БД, будет зависеть от того, что введено в UI-компонентах:
select e from uiex1_Person e where e.name like :name
select e from uiex1_Person e where e.status = :status
select e from uiex1_Person e where (e.name like :name) and (e.status = :status)