4.5.3.5. DsContext

Все созданные декларативно источники данных регистрируются в объекте DsContext экрана. Ссылку на DsContext можно получить методом getDsContext() контроллера экрана, либо инжекцией в поле класса.

DsContext решает следующие задачи:

  1. Позволяет организовать зависимости между источниками данных, когда при навигации по одному источнику (т.е. при изменении "текущего" экземпляра методом setItem()) обновляется связанный источник. Такие зависимости дают возможность в экранах легко организовывать master-detail связи между визуальными компонентами.

    Зависимости между источниками организуются с помощью параметров запросов с префиксом ds$.

  2. Позволяет собрать все измененные экземпляры сущностей и отправить их на Middleware в одном вызове DataManager.commit(), т.е. сохранить в базе данных в одной транзакции.

    В качестве примера предположим, что некоторый экран позволяет редактировать экземпляр сущности Order и коллекцию принадлежащих ему экземпляров OrderLine. Экземпляр Order находится в Datasource, коллекция OrderLine - во вложенном CollectionDatasource, созданном по атрибуту Order.lines. Допустим, пользователь изменил какой-то атрибут Order и создал новый экземпляр OrderLine. Тогда при коммите экрана в DataManager будут одновременно отправлены два экземпляра - измененный Order и новый OrderLine. Далее, они вместе попадут в один персистентный контекст и при коммите транзакции сохранятся в БД. Разумеется, экземпляр OrderLine содержится также в коллекции Order.lines, но если не передавать его в персистентный контекст независимо, то потребуется установка каскадности сохранения между Order и OrderLines на уровне ORM. Жесткие отношения каскадности на уровне ORM иногда вызывают нежелательные последствия в неожиданных местах, поэтому лучше их избегать, что и обеспечивает описываемый механизм DsContext.

    В результате коммита DsContext получает от Middleware набор сохраненных экземпляров (в случае оптимистической блокировки у них, как минимум, увеличено значение атрибута version), и устанавливает эти экземпляры в источниках данных взамен устаревших. Это позволяет сразу после коммита работать со свежими экземплярами без необходимости лишнего обновления источников данных, связанного с запросами к Middleware и базе данных.

  3. Объявляет два слушателя: BeforeCommitListener и AfterCommitListener, позволяющие получать оповещения перед коммитом измененных экземпляров и после него. Перед коммитом можно дополнить коллекцию отправляемых в DataManager на Middleware экземпляров, тем самым обеспечив сохранение в той же транзакции произвольных сущностей. После коммита можно получить коллекцию вернувшихся из DataManager сохраненных экземпляров.

    Данный механизм необходим, если некоторые сущности, с которыми работает экран, находятся не под управлением источников данных, а создаются и изменяются непосредственно в коде контроллера. Например, визуальный компонент FileUploadField после загрузки файла создает новый экземпляр сущности FileDescriptor, который можно сохранить вместе с другими сущностями экрана именно таким способом - добавив в CommitContext в слушателе BeforeCommitListener.

    В следующем примере новый экземпляр Customer будет отправлен на Middleware и сохранен в БД вместе с остальными измененными сущностями экрана при его коммите:

    protected Customer customer;
    
    protected void createNewCustomer() {
        customer = metadata.create(Customer.class);
        customer.setName("John Doe");
    }
    
    @Override
    public void init(Map<String, Object> params) {
        getDsContext().addBeforeCommitListener(context -> {
            if (customer != null)
                context.getCommitInstances().add(customer);
        }
    }