Пакетная обработка данных
До этого момента мы изучили использование внутренних процедур обработки событий для управления операциями заполнения и изменения. Но обработчики событий можно использовать и для пакетного извлечения данных с сервера. На производительность приложения оказывает влияние объём пересылаемых по сети данных. Если слишком большое количество (пакет) записей отправляется сразу, то пользователю вероятно придётся ожидать их получения. Если отправлено слишком мало записей, то для извлечения необходимых данных потребуется больше циклов.
Пакетирование данных, ещё это называют «батчинг» данных, означает получение подмножества данных с сервера с помощью отдельных серверных вызовов до тех пор, пока не будет извлечён весь требуемый объём. Наличие такой возможности для наборов данных позволяет решать проблему производительности приложения, работающего в сети. В зависимости от типа приложения, пользовательских требований и настроек сети, мы можем регулировать размер пакетов для набора данных.
Для реализации пакетирования рекомендуется использовать событие запроса OFF-END на стороне клиентского кода. Предположим, мы хотим, чтобы пользователь видел 10 записей в таблице, который были получены с сервера, и когда этот пользователь нажимает кнопку «Далее», с сервера извлекаются и выводятся на экран следующие 10 записей. Получение небольших пакетов данных позволяет пользователю увидеть данные немедленно не дожидаясь, когда всё содержимое таблицы будет доставлено с сервера. Когда запрос достигает конца последних извлечённых данных возникает состояние QUERY-OFF-END, которое генерирует событие OFF-END. В итоге обработчик этого события должен выполнить попытку получения следующего пакета данных с сервера. Если данных для извлечения больше нет, запрос завершает работу.
Для реализации пакетирования данных необходимо написать специальный код как на стороне клиента, так и на стороне сервера, руководствуясь следующим:
- На стороне клиента подготавливается код для получения первичного пакета данных, указывает размер пакет и ROWID первой записи в пакете.
- На стороне сервера тоже устанавливается размер пакета данных и ROWID первой записи в пакете. После чего данные извлекаются из базы данных и передаются клиенту вместе с ROWID следующей после последней записи в отправляемом пакете.
- На стороне клиента запрос ассоциируется с событием OFF-END. Затем запрос проверяет, достигнут ли конец итерации. И если состояние QUERY-OFF-END равно TRUE, то возникает событие OFF-END. После этого процедура обработки события OFF-END должна запросить следующий пакет данных с указанием начального ROWID и сбросить состояние QUERY-OFF-END.
Пример кода для получения первого пакета данных
В этом примере в коде на стороне клиента описаны переменная для хранения ROWID, которая указывает, где последний пакет закончился, и переменная с типом данных INTEGER, для указания размера извлекаемого пакета. Серверная процедура fetchOrders вызывается для извлечения 10 записей, которые добавляются во временную таблицу ttOrder набора данных dsOrder. При первом вызове этой процедуры переменная rowRestart имеет неопределённое значение. Серверный код использует это значение для старта операции заполнения набора с первой записи из таблицы базы данных. Завершив работу, процедура fetchOrders возвращает вместе с набором данных ROWID записи с которой должно быть продолжено извлечение следующего пакета. Обратите внимание, что этот код ещё не содержит обработку событий на клиенте для следующего вызова процедуры fetchOrders.
{include/dsOrder.i} DEFINE QUERY qryOrder FOR ttOrder. DEFINE VARIABLE rowRestart AS ROWID NO-UNDO. DEFINE VARIABLE batchSize AS INTEGER NO-UNDO INITIAL 10. RUN fetchOrders (batchSize, INPUT-OUTPUT rowRestart, OUTPUT DATASET dsOrder ). OPEN QUERY qryOrder FOR EACH ttOrder BY ttOrder.OrderNum. QUERY qryOrder:GET-NEXT(). DO WHILE NOT QUERY qryOrder:QUERY-OFF-END: MESSAGE "OrderNum:" ttOrder.OrderNum " Status: " ttOrder.OrderStatus. QUERY qryOrder:GET-NEXT(). END.
Поддержка пакетирования на сервере
Операция FILL по умолчанию извлекает все доступные записи для всех временных таблиц набора данных единым пакетом. Чтобы изменить это используется атрибут BATCH-SIZE для одной или всех временных таблиц в наборе. Кроме того, код сервера должен использовать атрибуты источника данных NEXT-ROWID и RESTART-ROWID для временной таблицы, чтобы указать, с какой записи должно начаться извлечение в пакете.
Атрибут BATCH-SIZE
Значение атрибута BATCH-SIZE представляет собой максимальное количество строк временной таблицы, которое должна извлечь операция FILL. По умолчанию значение BATCH-SIZE равно нулю (0), что сообщает набору данных об извлечении всех записей, удовлетворяющих запросу.
Для изменения значения атрибута установите новое значение BATCH-SIZE для одной или нескольких таблиц.
Как BATCH-SIZE работает с отношениями между таблицами
Представим, что у нас есть две временные таблицы в наборе данных, одну из которых мы назначаем дочерней по отношению к другой. Если указать размер пакета для дочерней таблицы, то этот размер будет использоваться для каждого подмножества дочерних записей, которые будут извлекаться для каждой родительской записи. Счётчик BATCH-SIZE будет перезапускаться для каждой родительской записи по мере прохождения по родительскому списку.
Атрибут NEXT-ROWID
NEXT-ROWID – это атрибут источника данных временной таблицы. После операции FILL этот атрибут будет содержать значение ROWID следующей записи из таблицы базы данных, когда FILL не извлекает все записи из таблицы. Код сервера должен получить это значение после завершения операции FILL и передать его клиенту.
Атрибут RESTART-ROWID
RESTART-ROWID – это атрибут источника данных временной таблицы. Он должен быть установлен перед началом операции FILL чтобы извлечение из таблицы базы данных началось с заданной записи.
Пример серверного кода для работы с пакетирование
Ниже приведён пример кода, который используется для пакетного извлечения данных на сервере. Небольшие пояснения к нему. Если значение переменной prowRestart не задано, то операция заполнения начнётся с начала таблицы. В противном случае значение переменной будет установлено в качестве значения атрибута RESTART-ROWID источника данных srcOrder. Размер пакета (атрибут BATCH-SIZE для временной таблицы) устанавливается равным значению входящего параметра pBatchSize. Как только этот атрибут установлен, вызывается операция FILL и соответствующее количество записей добавляется во временную таблицу. Если операция FILL достигнет конца таблицы базы данных, то атрибут временной таблицы LAST-BATCH будет установлен в значение TRUE. В завершение, переменная prowRestart получает с помощью атрибута NEXT-ROWID новое значение из источника данных временной таблицы, которое должно быть возвращено клиенту.
{include/dsOrder.i} DEFINE DATA-SOURCE srcOrder FOR Order. DEFINE INPUT PARAMETER pBatchSize AS INTEGER NO-UNDO. DEFINE INPUT-OUTPUT PARAMETER prowRestart AS ROWID NO-UNDO. DEFINE OUTPUT PARAMETER DATASET FOR dsOrder. BUFFER ttOrder:ATTACH-DATA-SOURCE(DATA-SOURCE srcOrder:HANDLE). IF prowRestart NE ? THEN DATA-SOURCE srcOrder:RESTART-ROWID = prowRestart. BUFFER ttOrder:BATCH-SIZE = pBatchSize. IF THIS-PROCEDURE:PERSISTENT THEN BUFFER ttOrder:FILL-MODE = "EMPTY". DATASET dsOrder:FILL(). prowRestart = DATA-SOURCE srcOrder:NEXT-ROWID. BUFFER ttOrder:DETACH-DATA-SOURCE(). RETURN.
Управление пакетированием на клиенте
Для реализации пакетирования на стороне клиента сначала вызывается серверная процедура для извлечения первого пакета записей, с указанием размера пакета и первой записи (ROWID), с которой должно начаться извлечение.
В примере ниже значение первой записи хранится в переменной rowRestart. Для извлечения первого пакета эта переменная не определена, что говорит о необходимости извлечения данных с начала таблицы.
RUN fetchOrders ( batchSize, INPUT-OUTPUT rowRestart, OUTPUT DATASET dsOrder).
При работе с пакетированием клиент проходит по извлечённым записям в наборе данных и когда запрос достигает состояния QUERY-OFF-END, возникает клиентское событие OFF-END, которое обрабатывается чтобы определить, возможно ли извлечение большего количества записей. Если возможно, то снова вызывается серверная процедура для добавления данных к набору. Это то место, где переменная rowRestart имеет значение. Её значение возвращается из предыдущего вызова серверной процедуры и должно быть использовано в следующем её вызове. Обработчик события также должен проверить существования данных для извлечения с сервера. Это делается через проверку атрибута временной таблицы LAST-BATCH. Если для извлечения больше нет записей, то операция FILL, которая выполняется на сервере, автоматически установит этот атрибут в возвращаемом клиенту наборе данных.
Пример управления пакетированием на клиенте
Рассмотрим пример клиентского кода для управления пакетированием. В нём мы связываем запрос с событием OFF-END и с внутренней процедурой offEnd, которая обрабатывает это событие. Обратите внимание на то, что внутренняя процедура находится в том же файле и мы не вызываем внешнюю процедуру. Обработчик события срабатывает, когда во время очередной итерации возникает состояния QUERY-OFF-END. Входящим параметром для обработчика является набор данных.
В коде обработчика события для временной таблицы ttOrder мы проверяем атрибут LAST-BATCH. Если он имеет значение TRUE, то вызывается процедура fetchOrders, которой передаётся значение rowRestart, которое было получено из последнего вызова этой процедуры. Для набора данных, который передаётся процедуре в качестве параметра, мы указали APPEND чтобы новый пакет данных был добавлен к существующим данным в наборе. После завершения процедуры fetchOrders наш код должен вернуть NO-APPLY. Это сбросит состояние QUERY-OFF-END в FALSE и извлечение записи продолжится до тех пор, пока все записи не будут получены с сервера.
{include/dsOrder.i} DEFINE QUERY qryOrder FOR ttOrder. DEFINE VARIABLE rowRestart AS ROWID NO-UNDO. DEFINE VARIABLE batchSize AS INTEGER NO-UNDO INITIAL 10. /* Установка внутренней процедуры обработки события, для обработки события OFF-END, тем самым указывая на то, что запросу нужен новый пакет данных*/ QUERY qryOrder:SET-CALLBACK-PROCEDURE("OFF-END","offEnd"). /* Получаем первый пакет данных, передав ROWID записи, с которой начнётся извлечение В данном случае передаётся неопределённое значение (?), т.е. чтение записей с начала таблицы */ RUN fetchOrders ( batchSize, INPUT-OUTPUT rowRestart, OUTPUT DATASET dsOrder APPEND). OPEN QUERY qryOrder FOR EACH ttOrder BY ttOrder.OrderNum. QUERY qryOrder:GET-NEXT(). DO WHILE NOT QUERY qryOrder:QUERY-OFF-END: MESSAGE "OrderNum:" ttOrder.OrderNum " Status: " ttOrder.OrderStatus. QUERY qryOrder:GET-NEXT(). END. PROCEDURE offEnd: DEFINE INPUT PARAMETER DATASET FOR dsOrder. IF NOT BUFFER ttOrder:LAST-BATCH THEN DO: MESSAGE "Получение большего количества данных с сервера.......". RUN fetchOrders ( batchSize, INPUT-OUTPUT rowRestart, OUTPUT DATASET dsOrder APPEND ). /*Использование RETURN NO-APPLY необходимо для отмены эфекта OFF-END в запросе. Это позволит итерации продолжить обработку события OFF-END и переоткрыть запрос, автоматически спозиционировавшись на следующей записи, которая только что получена с сервера */ RETURN NO-APPLY. END. ELSE MESSAGE "Больше нет данных.......". END PROCEDURE.
Существуют ещё два дополнительных события набора данных, которые могу возникать как на клиенте, так и на сервере:
Событие | Когда возникает | Возможное применение |
FIND-FAILED | Происходит во время навигации по запросу, если дочерняя запись, связанная с родительской записью, не существует в наборе данных.
Это может произойти если заполнение набора данных не выполнялось рекурсивно. |
|
SYNCHRONIZE | Происходит, когда приложение вызывает метод SYNCHRONIZE () для перемещения временной таблицы в наборе. Событие запускается непосредственно перед синхронизацией соответствующих буферов путём повторного открытия запросов дочернего буфера. Событие также запускается при закрытии запроса. |
|
Предлагается самостоятельно изучить использование этих дополнительных событий с помощью документации по ProDataSets.