33.1. Использование кэшированных изменений
Кэширование изменений заключается в том, что создается локальная копия данных в буфере (кэше). Все последующие корректировки данных, включая изменение, удаление, добавление новых записей, происходят в этом локальном буфере. Внесенные изменения могут быть физически перенесены в БД, или может быть произведен отказ от запоминания ("откат").
К преимуществам способа кэшированных изменений перед транзакционным способом можно отнести:
• минимизацию сетевого трафика при работе с удаленным сервером;
• отсутствие блокировок на изменяемые записи
Минимизация сетевого трафика при работе с удаленным сервером. При транзакционном способе каждая транзакция вызывает передачу на сервер пакета изменений При кэшированных изменениях все изменения передаются как один пакет.
Отсутствие блокировок на изменяемые записи. В зависимости от уровня изоляции транзакций, на измененные записи, изменение которых еще не подтверждено, может быть наложена блокировка. Например, при уровне изоляции
ReadCommited блокируется возвращение результатов выполнения запросов к ТБД, если в НД попадают измененные другим пользователем записи, транзакция по которым еще не завершена. Поскольку кэшированные изменения производятся в локальном буфере клиента, блокировки на измененные записи не накладываются. Впрочем, это может создавать неудобства в том случае, когда имеется жесткое требование на блокировки измененных записей. Принятие решения о предпочтительности использования кэшированных изменений в таких случаях зависит от конкретных особенностей предметной области и приложения33.2. Активизация режима кэшированных изменений
Кэшированные изменения могут быть включены для наборов данных (компоненты
TTable, TQuery, TStoredProc) путем установки в True свойстваproperty CachedUpdates: Boolean;
Установка значения данного свойства может быть произведена на этапе разработки приложения и впоследствии переопределена во время работы приложения.
Отмена режима кэшированных изменений осуществляется путем установки данного свойства в
FalseTovary. CachedUpdates := True;
...
Tovary. CachedUpdates := False;
33.3. Отмена кэшированных изменений
Сделанные изменения в НД (компоненты
TTable, TQuery, TStoredProc) могут быть отменены методомprocedure CancelUpdates;
33.4. Подтверждение кэшированных изменений
Подтверждение кэшированных изменений реализуется в 2 фазы. Двухфазный подход имеет своей целью обеспечить восстановление БД от ошибок, особенно в случае внесения изменений в несколько ТБД. Например, при кэшированных изменениях не обнаруживаются нарушения ограничения ссылочной целостности таблиц. Они обнаруживаются только при попытке запомнить такие изменения
Запускается транзакция БД;
Кэшированные изменения записываются в БД.
В случае успешной записи кэшированных изменений в БД:
• происходит подтверждение транзакции БД;
• подтверждаются кэшированные изменения.
В случае неуспешной записи кэшированных изменений в БД:
• происходит откат транзакции БД;
• происходит откат кэшированных изменений, с восстановлением или без восстановления первоначальных значений записей Пользователь может скорректировать ситуацию, вызвавшую ошибку, и повторить попытку запоминания кэшированных изменений в БД.
33.4.1. Подтверждение кэшированных изменений методом
ApplyUpdates компонента TDatabaseМетод
компонента TDatabaseprocedure ApplyUpdates(const DataSets: array ofTDataSet);
применяется для подтверждения кэшированных изменений сразу в нескольких НД Список НД определяется параметром
DataSets В случае указания нескольких НД их имена разделяются запятымиДвухфазное запоминание в этом случае производится неявно' метод
ApplyUpdates стартует транзакцию и пытается запомнить измененные записи в БД В случае неудачи, он откатывает транзакцию, но не изменяет статуса кэшированных изменений. Поэтому, если необходимо отменить кэшированные изменения, это необходимо сделать явно при помощи метода НД CancelUpdates. Управление ошибкой запоминания кэшированного изменения может осуществляться в обработчике события OnUpdateError. В случае успешного запоминания результатов кэшированных изменений в БД метод Apply Updates подтверждает транзакцию и подтверждает кэшированные изменения.Пример. Пусть требуется подтвердить кэшированные изменения НД
Tovary и Rashod. В случае неудачи необходимо откатить кэшированные изменения:TRY
DataBasel.ApplyUpdates([Tovary, Rashod]);
EXCEPT
ShowMessage
('Изменения сохранить нельзя: они влекут нарушение ' + 'целостности базы данных') ;Tovary.CancelUpdates ;
Rashod.CancelUpdates;
END;//try
33.4.2. Подтверждение кэшированных изменений методом
ApplyUpdates набора данныхМетод компонентов
TTable, TQuery, TStoredProcprocedure ApplyUpdates;
применяется для подтверждения кэшированных изменений отдельного НД. Отсутствие ошибок при выполнении метода гарантирует успешность операции подтверждения.
Метод компонентов
TTable, TQuery, TStoredProcprocedure CommitUpdates;
применяется для обновления в БД кэшированных изменений, успешно подтвержденных методом
ApplyUpdates.Двухфазное подтверждение в случае использования методов
ApplyUpdates и CommitUpdates необходимо реализовывать явно.Пример. Пусть требуется подтвердить кэшированные изменения НД
Tovary и Rashod. В случае неудачи необходимо откатить кэшированные изменения:Databasel.StartTransaction;
TRY
Tovary.ApplyUpdates ;
Rashod.ApplyUpdates;
Databasel.Commit;
Tovary.CommitUpdates;
Rashod.CommitUpdates ;
EXCEPT
Databasel.Rollback;
ShowMessage
('Изменения могут привести к нарушению ' + 'целостности БД. Изменения отменены!' );Tovary.CancelUpdates ;
Rashod.CancelUpdates ;
END;//try
В
случае сбоя при выполнении Tovary.Apply Updates или Rashod.ApplyUpdates не произойдет подтверждения транзакции (Database]. Commit) и подтверждения кэшированных изменений для каждой из таблиц; значения измененных записей будут восстановлены в значения, имевшие место до изменений (Tovary.CancelUpdates, Tovary. CancelUpdates).Часто важно, чтобы даже в случае неудачной попытки запомнить изменения, значения измененных записей не менялись - например, для того чтобы пользователь мог внести коррективы и попытаться снова подтвердить изменения. В этом случае из кода необходимо убрать вызов метода
CancelUpdates:Database1.StartTransaction;
TRY
Tovary.ApplyUpdates ;
Rashod.ApplyUpdates;
Databasel.Commit;
Tovary.CommitUpdates;
Rashod.CommitUpdates ;
EXCEPT
Databasel.Rollback;
ShowMessage
('Изменения могут привести к нарушению ' + 'целостности БД. Изменения отменены!' );END;//try
33.5. Видимость измененных записей
Свойство
набора данныхproperty UpdateRecordTypes: TUpdateRecordTypes;
TUpdateRecordTypes = set of (rtModified, rtlnserted, rtDeleted, rtUnModified);
указывает, какие из записей будут "видны" в НД после проведения кэшированных изменений. Множество
TUpdateRecordTypes может содержать следующие значения:rtModified -
измененные записиrtlnserted -
добавленные записиrtDeleted -
удаленные записиrtUnmodified-
неизменявшиеся записиПример. Показать в НД только удаленные записи:
RashodTable.UpdateRecordTypes := [rtDeleted];
Пример.
Показать в НД только неизменявшиеся, добавленные и измененные записи (удаленные не показывать):RashodTable.UpdateRecordTypes := [rtUnmodified,rtModified, rtlnserted];
ЗАМЕЧАНИЕ.
Хотя в НД могут показываться и удаленные записи, тем не менее свойство НД RecordCount возвратит количество записей за вычетом удаленных.33.6. Обработка ошибок. Обработчик события
OnUpdateError33.6.1. Использование обработчика OnUpdateError
К наиболее часто встречающимся ошибкам, происходящим при попытках подтверждения кэшированных изменений, относятся:
• нарушение значения поля связи в родительской или дочерней таблицах;
• нарушение уникальности индекса, построенного по первичному или уникальному ключу таблицы БД;
• попытка ввести в поле связи дочерней таблицы значение, отсутствующее в поле связи родительской таблицы;
• нарушение требования ввода в одно из полей обязательного значения;
• нарушение ограничений, накладываемых на значение поля.
Реагировать на ошибки, возникающие при подтверждении кэшированных изменений, можно в обработчике события
OnUpdateError:procedure TForm1.TovaryUpdateError(DataSet: TDataSet;
E: DatabaseError; UpdateKind: TUpdateKind;
var UpdateAction: TUpdateAction);
begin
...
end;
где параметры имеют следующее назначение:
33.6.2. Использование параметра
UpdateKindПараметр
UpdateKind позволяет в случае ошибки предпринять какие-либо действия, зависящие от способа изменения записи.Пример. Если произошла ошибка при попытке подтверждения кэшированного удаления записи, нужно восстановить запись:
procedure TForm1.TovaryUpdateError(DataSet: TDataSet;
E: EDatabaseError; UpdateKind: TUpdateKind;
var UpdateAction: TUpdateAction);
begin
IF UpdateKind = ukDelete THEN Tovary.RevertRecord;
end;
33.6.3. Использование параметра
UpdateActionИзменяемый параметр
UpdateAction определяет действие, которое следует произвести в отношении ошибочной записи. Действие указывается перед выходом из обработчика:procedure TForm1.TovaryUpdateError(DataSet: TDataSet;
E: EDatabaseError; UpdateKind: TUpdateKind;
var UpdateAction: TUpdateAction);
begin
// какие-либо операторы, если нужны
UpdateAction := uaSkip;
end;
Можно указать следующие действия:
Значение |
Что влечет |
uaFail |
Отменить изменения для записи, выдать сообщение об ошибке. |
uaAbort |
Отменить изменения для записи, сообщение об ошибке не выдавать. Применяется также в случаях, когда генерируется сообщение об ошибке, отличное от выдаваемого системой. |
uaSkip |
Не отменять изменений для записи, но запись физически в таблицу БД не переносить (что, собственно, и невозможно без изменения значений записи, повлекших ошибку). |
uaRetry |
Повторить попытку подтверждения изменений для записи. Предполагается, что на момент повторения попытки значения полей данной записи скорректированы программным способом. В противном случае произойдет зацикливание. |
Можно заметить, что при применении способа обработки ошибочной записи
uaSkip в физической ТБД будут подтверждены изменения не всех записей. а только тех из изменений, для которых не произошла ошибка при подтверждении кэшированных изменений. Такой подход противоречит транзакционному подходу, согласно котором) либо запоминаются все изменения, либо происходит их одновременный откатПример. Пусть имеется таблица "Товары" и дочерняя таблица "Расход товара". Пусть эти таблицы соединены по полю связи "Товар"(рис. 33.1).
Как видно из рисунка, в таблице "Товары" для товаров "Сахар" и "Сыр гудаутский" имеются дочерние записи в таблице "Расход". Изменим в таблице "Товары" наименования всех товаров и попытаемся подтвердить кэшированные изменения:
Database1.StartTransaction;
TRY
Tovary.ApplyUpdates;
Databasel.Commit;
Tovary.CommitUpdates;
EXCEPT
Databasel.Rollback;
END;//try
Тогда изменение значения в поле "Товар" для товаров "Сахар" и "Сыр гудаутский" приведет к нарушению целостности БД, поскольку в таблице "Расход" наименования товаров в поле "Товар" мы изменять не будем.
Напишем обработчик
On UpdateError для НД "Товар":procedure TForm1.TovaryUpdateError(DataSet: TDataSet;
E: EDatabaseError; UpdateKind: TUpdateKind;
var UpdateAction: TUpdateAction) ;
begin
CASE RadioGroup1.Itemlndex OF
0 : UpdateAction := uaFail;
1 : UpdateAction := uaAbort;
2 : UpdateAction := uaSkip;
3 : UpdateAction := uaRetry;
END;//case
end;
Компонент
RadioGroupl содержит возможность выбора одного из значений изменяемого параметра UpdateAction.Изменяя значение параметра
UpdateAction, посмотрим, изменение каких записей будет подтверждено при возникновении ошибки при выполнении метода Tovary ApplyUpdates',•
UpdateAction = uaFail. Как показано на рис.33.2.а) и б), в режиме uaFail результаты кэшированных изменений полностью отменены:Рис 332 а) до подтверждения изменений (
UpdateAction = uaFail). б) после подтверждения изменений, открытия и закрытия НД "Товары " (UpdateAction = uaFail)•
UpdateAction = uaAbort. Как показано на рис.33.3.а) и б), в режиме uaAbort результаты кэшированных изменений полностью отменены:Рис 33.3 а) до подтверждения изменений (
Update Action =uaAbort), б) noc-ie подтверждения изменений, открытия и закрытия НД "Товары" (UpdateAclion = uaAbort)UpdateAction = uaSkip.
Как показано на рис.34.а) и б), в режиме uaSkip результаты кэшированных изменений отменены частично:Рис 33.4 а) до подтверждения изменений (UpdateAclion = uaSkip), б) после подтверждения изменений, открытия и закрытия НД "Товары" (UpdateAction
= uaSkip)UpdateAction = uaRetry.
Произойдет зависание приложенияРис 33.5. а) до подтверждения изменений (
UpdateAction = uaRetry), 6) после подтверждения изменений, открытия и закрытия НД "Товары" (UpdateAction = uaRetry)Причина зависания проста: при
UpdateAction = uaRetry происходит попытка заново подтвердить квитированные изменения для записи, на которой произошла ошибка; однако поскольку значение поля "Товар" не изменилось, повторная попытка вызовет ошибку, для которой назначено действие новой попытки подтверждения, и т.д.Введем в обработчик события
OnUpdateError для режима UpdateAction = uaRetry возврат к старому значению поля "Товар" для записей, на которых попытка подтверждения изменений вызывает ошибку:procedure TForm1.TovaryUpdateError(DataSet: TDataSet;
E: EDatabaseError; UpdateKind: TUpdateKind;
var UpdateAction: TUpdateAction);
begin
CASE RadioGroup1.ItemIndex OF
0 : UpdateAction := uaFail;
1 : UpdateAction := uaAbort;
2 : UpdateAction := uaSkip;
3 : begin
Tovary.FieldByName('Tovar').NewValue :=Tovary.FieldByName('Tovar').OldValue;
UpdateAction := uaRetry;
end;
END;//case
end;
Тогда обработчик
OnUpdateError будет действовать, как если бы было установлено UpdateAction = uaSkip (рис. ЗЗ.6.а) и б)).Рис 33.6 а) до подтверждения изменений (
UpdateAction = uaRetry) с откатом ошибочных записей к старому значению, б) после подтверждения изменений, открытия и закрытия НД "Товары" (UpdateAction = uaRetry) с откатомошибочных записей к старому значению
33.6.4. Использование параметра Е
Используя параметр Е, можно определить причину возникновения ошибки и в соответствии с этой причиной предпринять какие-либо действия.
Параметр Е содержит ссылку на исключительную ситуацию, возбужденную при диагностировании ошибки в процессе подтверждения кэшированных изменений.
Параметр Е: свойство
MessageТип исключения, на которое ссылается Е, -
EDatabaseError. Код ошибки в этом типе отсутствует, но имеется свойствоproperty Message: string;
которое содержит текст сообщения об ошибке, выводимого на экран при возбуждении исключительной ситуации.
Пример. Пусть известно, что при подтверждении кэшированных изменений могут возникнуть ошибки двух типов: нарушение уникальности первичного ключа и нарушение целостности БД вследствие одностороннего изменения в родительской таблице поля связи. Тогда можно предложить такой обработчик
On UpdateError:procedure TForm1.TovaryUpdateError(DataSet: TDataSet;
Е
: EDatabaseError; UpdateKind: TUpdateKind;var UpdateAction: TUpdateAction);
var S : String;
PartCat, PartCode : Word;
begin
// Обработка ошибки через
MessageIF POS('FOREIGN',E.Message) > 0 THEN
S
:= 'Нарушение целостности БД. Для старого значения поля TOVAR '+ 'есть дочерние записи в таблице "Расход" '
ELSE
IF POS('Key violation',E.Message) > 0 THEN
S
:= 'Нарушение уникальности поля ТОВАР'ELSE
S := '
Другая ошибка';ShowMessage (S);
end;
Параметр Е : свойство
ErrorCodeЕсли привести параметр Е к типу
EDBEngineError простой операцией приведения типов:EBDEngineError
(Е)можно воспользоваться свойствами класса
EDBEngineError:property Errors(Index: Integeri: TDBError; - список ошибок в стеке ошибок Borland Database Engine. Номер первой ошибки 0. Номер последней ошибки равен значению свойства ErrorCount, уменьшенному на 1.
property ErrorCount: Integer; -
содержит общее число ошибок в стеке BDE.property ErrorCode: DBIResult; -
возвращает код ошибки типа Word.В каталоге, в котором располагается
Delphi, в подкаталоге \DOC\ в файле bde.int определяются коды ошибок, возвращаемые методом ErrorCode. Код ошибки имеет тип Word и обычно определяется как сумма категории ошибки и самой ошибки.Код категории ошибки можно получить при помощи функции (модуль
BDE.pas)function ErrCat(ErrorCat: Word): Word;
Код ошибки внутри данной категории можно получить при помощи функции (модуль
BDE.pas)function ErrCode(ErrorCode: Word): Word;
Пример
использования:procedure TForm1.TovaryUpdateError(DataSet: TDataSet; Е:
EDatabaseError;
UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
var S : String;
PartCat, PartCode : Word;
begin
S:=' '
//обработка ошибки через
ErrCodeIF Е is EDBEngine
Error THENWITH EDBEngineError (Е) do begin
PartCat := ErrCat(EDBEngineError(Е).Errors
[ErrorCount - 1].ErrorCode);
PartCode := ErrCode(EDBEngineError(Е).Errors
[ErrorCount - 1].ErrorCode);
IF PartCat = ERRCAT_INTEGRITY THEN
S
:= 'Нарушение целостности базы';IF PartCat = ERRCAT_DATACORRUPT THEN
S
:= 'Физическое разрушение данных';IF PartCat = ERRCAT_IO THEN
S
:= 'Ошибка операции ввода-вывода';{...}
IF PartCat = ERRCAT_OTHER THEN S := '
Ошибка категории ERRCAT_OTHER';ShowMessage('
Произошла ошибка типа = ' + S) ;END;//with
end;
Свойства
OldValue и New ValueПри использовании кэшированных изменений доступны следующие свойства компонента
Tfield: поле таблицы БД или для серверных СУБД -столбец таблицы БД.property OldValue: Variant;
Содержит исходное значение поля (столбца), то есть значение, имевшее место до каких-либо изменений. После того, как кэшированные изменения были успешно подтверждены, старое значение поля (столбца) извлечь становится невозможным.
property New Value: Variant;
Содержит новое значение поля (столбца) после внесения в поле каких-либо изменений. В том случае, когда при попытке подтверждения кэшированных изменений происходит ошибка, в обработчике
OnUpdateError можно программно скорректировать значение New Value и повторить попытку подтверждения изменений, указав UpdateAction = uaRetry. TUpdateSQL33.7.1. Использование компонента
TUpdateSQLКомпонент
TUpdateSQL используется для подтверждения кэшированных изменений в НД, связанном с компонентом TQuery. Такой НД является результатом выполнения SQL-оператора SELECT и может быть доступным для обновления или доступным только для чтения. В последнем случае свойство НДproperty CanModify: Boolean;
(реальная возможность модификации НД) возвращает False, даже если свойство компонента TQueryproperty RequestLive: Boolean;
(разрешение модификации НД) установлено в True на этапе разработки приложения или программно во время выполнения.Компонент
TUpdateSQL позволяет вносить изменения в НД, доступные только для чтения. Этот компонент как бы объединяет в себе три компонента TQuery, содержащих SQL-операторы INSERT, UPDATE, DELETE для соответственно добавления, изменения или удаления записи. TUpdateSQL: этап разработки 1. Разместите в форме компонент TUpdateSQL.2.
Выберите компонент TQuery, кэшированные изменения в котором должен подтверждать компонент TUpdateSQL. Свойство TQuery. CachedUpdates должно быть установлено в True.3. Укажите в свойстве
TQuery. UpdateObject имя компонента TUpdateSQL.4.
Выберите мышью (сделайте текущим) компонент TUpdateSQL и дважды щелкните на нем мышью. На экране появится редактор свойств TUpdateSQL (рис. 33.7).5. В списке
Table Name выберите таблицу БД, в которой необходимо подтверждать кэшированные изменения.6. В окне А'еу
Fields выберите поля, входящие в индекс, который следует использовать при обновлении записи. Для локальных СУБД по указанным полям должен быть построен реально существующий индекс;для удаленных СУБД существование такого индекса желательно, но не обязательно.
7. Если нужно выделить поля, входящие в первичный ключ таблицы, нажмите кнопку
Primary Key Fields.8. В окне Update Fields выделите поля, значения в которых следует обновлять;
9. Нажмите кнопку Generate SQL, чтобы сгенерировать SQL-операторы для добавления, изменения и удаления записей.
10. Перейдите на страницу SQL и просмотрите три сгенерированных оператора - INSERT, UPDATE, DELETE (рис. 33.8).
11. Измените сгенерированные операторы, если это необходимо.
Сгенерированные SQL-операторы содержатся в свойствах компонента TUpdateSQL
property InsertSQL: TStrings;
property ModifySQL: TStrings;
property DeIeteSQL: TStrings;
TUpdateSQL: выполнение SQL-операторовСуществует два способа выполнения SQL-операторов, определенных в компоненте
TUpdateSQL.Автоматическое выполнение SQL-операторов компонента
TUpdateSQLПри использовании одного компонента
TUpdateSQL для занесения подтвержденных изменений в ТБД следует использовать автоматический способ. Он заключается в том, что применение метода TDatabase. Apply Updates для каждой подтвержденной записи приводит к автоматическому выбору и выполнению соответствующего SQL-оператора из компонента TUpdateSQL, ассоциированного с одним из изменяемых НД.В этом случае не нужно кодировать вызов SQL-операторов из компонента
TUpdateSQL.Использование обработчика события
OnUpdateRecord для вызова SQL-операторов компонента TUpdateSQLЕсли с НД связано несколько компонентов
TUpdateSQL, вызов SQL-оператора из нужного компонента TUpdateSQL производят в обработчике события OnUpdateRecord. Параметры обработчика OnUpdateRecord идентичны параметрам рассмотренного выше обработчика события On UpdateError за исключением отсутствующего параметра Е:procedure TFormI.QuerylUpdateRecord(DataSet: TDataSet;
UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
begin
end;
Для установки значений параметров изменяемой записи используют метод
SetParams компонента TUpdateSQL:procedure SetParams(UpdateKind: TUpdateKind);
UpdateKind - указывает тип действия:
ukModify -
запись была изменена (скорректировано значение хотя бы одного поля);uklnsert -
запись была добавлена;ukDelete -
запись была удалена.Метод
SetParams замещает параметры в SQL-операторе компонента TUpdateSQL значениями соответствующих полей ТБД. Какой SQL-оператор выполнять, определяет значение UpdateKind.Для выполнения соответствующего SQL-оператора используют метод
ExecSQL компонента TUpdateSQL:procedure ExecSQL(UpdateKind: TUpdateKind);
где
UpdateKind указывает тип действия.Существует также метод
Apply, который объединяет в себе функциональность методов SetParams и ExecSQL:procedure Apply(UpdateKind: TUpdateKind);
Метод
Apply в соответствии со значением параметра UpdateKind определяет требуемый SQL-оператор, изменяет параметры этого оператора и затем выполняет его.Пример. Обработчик события OnUpdateRecord, содержащий вызов соответствующих SQL-операторов компонента TUpdateSQL:
procedure TFormI.QuerylUpdateRecord(DataSet: TDataSet;
UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
begin
WITH DataSet.UpdateObject as TUpdateSQL do begin
SetParams(UpdateKind) ;
ExecSQL(UpdateKind) ;
END;//with
UpdateAction := uaApplied;
end;
ЗАМЕЧАНИЕ.
Значение uaApplied изменяемого параметра UpdateAction обработчика On UpdateRecord подтверждает изменения в данной записи Этот обработчик может иметь более широкое применение, однако поскольку возможности этого обработчика не всегда вписываются в транзакционный способ изменений в БД, в настоящем материале они не рассматриваются; более подробно с этими возможностями можно ознакомиться в Borland Delphi Database Application Developer's Guide