Учебная работа. Реферат: Перенос приложений MIDAS с одной СУБД на другую

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (5 оценок, среднее: 4,80 из 5)
Загрузка...
Контрольные рефераты

Учебная работа. Реферат: Перенос приложений MIDAS с одной СУБД на другую

Перенос приложений MIDAS с одной СУБД на другую

Александр Капустин

Введение

В данной статье рассматриваются препядствия, связанные с миграцией приложения MIDAS с одной СУБД на другую. Разглядим это на примере переноса приложения, описанного в статье Романа Игнатьева «MIDAS: практика внедрения». приложение написано под Interbase 5.6 и употребляет составляющие IBX на сервере приложений для доступа к СУБД. Перепишем его таковым образом, чтоб приложение сумело работать под управлением MSSQL Server 7.0 и MSSQL Server 2000 (с помощью маленьких переделок скрипта можно достигнуть работы приложения под Sybase ASE 12.0). Следует также увидеть, что переделке подвергнутся лишь скрипт СУБД и сервер приложений. Клиентская часть остается нетронутой, т.к. при использовании многозвенной архитектуры она полностью изолирована от деталей реализации серверной части.

Некие замечания к содержанию статьи.

Предполагается, что читателю уже знакомы (хотя бы в исходной стадии) синтаксис SQL (в приложении или к Interbase, или к MSSQL, также общие принципы работы с БД из Delphi (в статье употребляются IBX&ADO, но это не единственно вероятное решение).

процесс переноса происходит уже опосля того, как приложение отлажено и размеренно работает (т.к. параллельная разработка для нескольких СУБД – мало иной вариант, и его мы разглядывать не будем).

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

Приобретается в главном понижение цены программного продукта, ибо если у клиента уже установлена хотя бы одна из поддерживаемых вами СУБД, нет необходимости растрачивать огромные средства на закупку СУБД, новейшего сервера и настройку всего этого. Улучшаются способности интеграции с существующими системами.

Но при всем этом мы перестаем применять на 100% способности какой-нибудь отдельной СУБД. Следует различать два варианта. 1-ый вариант, когда поддерживается сопоставимость со старенькыми версиями данной нам же СУБД (к примеру, поддерживается линейка MSSQL 6.5 – MSSQL2000). В этом случае мы ограничены способностями самой слабенькой версии, и не можем применять нововведения. 2-ой вариант, еще наиболее тяжкий и вкупе с тем увлекательный для рассмотрения, когда планируется сопоставимость меж разными СУБД, к примеру меж MSSQL и Interbase. должен сходу обмолвиться, что этот вариант встречается еще пореже, но если вы при проектировании приложения не будете учесть эту возможность, то переход вызовет еще больше сложностей.

Необходимо подчеркнуть, что при переносе двухуровневого приложения заморочек возникнет еще больше, т.к. большая часть бизнес-логики находится на сервере, и если синтаксис СУБД очень различается, способности переноса очень ограничиваются. В случае же трехуровневого приложения большая часть задач, связанных с логикой, решает приложений (охото увидеть, что это справедливо лишь для верно спроектированного приложения).

Модификация структуры БД

К огорчению, перенос структуры БД «один-в-один» меж разными СУБД фактически неосуществим. тут приведено описание неких заморочек, с которыми придется столкнуться при переносе, также вероятные пути их решения. лучше всего, если б эти вещи были учтены вначале при проектировании начального приложения, т.к. в этом случае размер работ при переносе приложения сокращается.

Добавление записей в таблицу

Для абстрагирования способа генерации неповторимых идентификаторов для каждой записи можно вынести его в хранимую функцию, которая будет возвращать ID новейшей записи. Это дозволяет просто добавлять записи в подчиненную таблицу, не производя никаких доп манипуляций. В предстоящем вы сможете возложить на эту функцию, к примеру, генерацию идентификаторов в данном спектре, либо обеспечить сквозную нумерацию. Для хранения идентификаторов проще всего иметь отдельную таблицу приблизительно последующего вида:

create table Seeds (

TableName varchar(30), —имятаблицы

ID int, —ID крайней вставленной записи в данную таблицу

LowOffset int —нижняя граница спектра

)

go




При добавлении пользовательских таблиц нужно не забывать вставлять в эту таблицу надлежащие записи. Ниже приведен пример SQL-запроса, делающего это:

insert into Seeds(TableName, ID, LowOffset, HiOffset)

values(‘MyCoolTable’, 0, 0, 1000000)

go




текст процедуры в простом случае будет смотреться так:

create procedure CLIENT_ID

@TableName varchar(30),

@ID int output

as

update Seeds

set ID = ID + 1,

@ID = ID + LowOffset

where TableName = @TableName

go




При обновлении (update) таблицы накладывается блокировка конфигурации, которая не дозволит другому клиенту выполнить эту же функцию сразу с первым. Кроме перечисленного выше таковой подход упрощает жизнь по мере необходимости репликации данных меж филиалами. Тогда в любом филиале настраивается собственный спектр, и первичные ключи гарантированно не будут пересекаться.

Контроль целостности данных

При проектировании базы нужно учитывать последующие ограничения:

Каскадное изменение по foreign key возникло лишь в MSSQL2000. Так что если задаться целью сохранить сопоставимость с прошлыми версиями (также с Sybase), каскадные конфигурации нужно создавать с помощью хранимых процедур (почему не применять триггеры, сказано ниже).

Триггеры, отрабатывающие не опосля проверки всех ограничений целостности, а заместо деяния, на которое их вызвали, также возникли лишь в MSSQL2000. В наиболее ранешних версиях они просто не смогли бы отработать каскадное изменение при наличии foreign key. Также при написании триггеров следует учитывать индивидуальности реализации для каждой СУБД. Так, к примеру, в Interbase триггер отрабатывает на каждую запись, а в MSSQL – на изменение, вставку либо удаление записи. Как вариант, можно отрешиться от поддержки целостности, основанной на foreign key, и воплотить ее на сто процентов на триггерах.

Перенос скрипта

тут приведены главные трудности, с которыми можно столкнуться при переносе скрипта Interbase на MSSQL (должен снова повториться, что статья не претендует на полный и детализированный разбор различий меж этими СУБД, да таковой анализ и не быть может на сто процентов корректным).

Соответствие интегрированных типов

Главные различия, которые следует учесть при переносе скрипта:


IB
MSSQL
Комментарий

char
char
-в MSSQL – не наиболее 8000, в IB — не наиболее 32767 char

varchar
varchar
-в MSSQL — не наиболее 8000, в IB — не наиболее 32767 char

blob
text, image

date
datetime, smalldatetime
(крайний обрезает время до минут)

money, smallmoney
— в IB нет аналогов

bit
— в IB нет аналогов

В MSSQL нет типов, представляющих лишь дату либо лишь время, имеющихся в IB6.

Домены

В IB для сотворения доменов употребляется последующая система:


create Domain DCount numeric(15,4) default 1 not null;

Для MSSQL это будет смотреться последующим образом:

create default ONE as 1

go

exec sp_addtype ‘DCount’, ‘numeric(15,4)’, ‘NOT NULL’

go

exec sp_bindefault ‘ONE’, ‘DCount’

go




Таблицы

Переносятся без заморочек, следует лишь направить внимание на замечания по контролю целостности данных (к слову, оборотное преобразование будет затруднено, если вы будете применять специальные для MSSQL типы (в особенности для MSSQL2000)).

Хранимые процедуры

Перенос хранимых процедур – это более трудозатратный процесс, т.к. придется переписывать все полностью. Но в верно спроектированном трехзвенном приложении роль ХП обязана быть сведена к минимуму. Главные трудности появляются при переводе ХП, возвращающих результирующий набор. часть из их (не содержащие сложной бизнес-логики) быть может переведена в разряд представлений (view). Для других можно или создавать временные таблицы на уровне соединения с СУБД, или создавать неизменные таблицы и разграничивать данные в их по идентификатору подключения (SPID) (но тогда не запамятовывайте их чистить :)). Если же вы решите ограничиться лишь MSSQL2000, то сможете применять тип «таблица» для возврата набора значений из процедуры. Разглядим несколько примеров перевода ХП. Процедура отчета о взаиморасчетах меж клиентами:

create procedure REP_INOUT(FROM_DATE date, TO_DATE date)

returns (FROM_ID integer, FROM_NAME varchar(180), TO_ID integer,

TO_NAME varchar(180), FULL_SUM numeric(15,4))

as

begin

for select FROM_ID, TO_ID, sum(DOC_SUM)

from DOC_TITLE

where DOC_DATE >= :FROM_DATE and DOC_DATE <= :TO_DATE

group by FROM_ID, TO_ID

into :FROM_ID, :TO_ID, :FULL_SUM

do begin

FROM_NAME = NULL;

TO_NAME = NULL;

select NAME

from client

where CLIENT_ID = :FROM_ID

into :FROM_NAME;

select NAME

from client

where CLIENT_ID = :TO_ID

into :TO_NAME;

if (FULL_SUM is NULL) then

FULL_SUM = 0;

suspend;

end

end

^




Преобразуется в функцию последующего вида:

create procedure rep_inout @from_date smalldatetime, @to_date smalldatetime

as

select dt.from_id, dt.to_id, isnull(sum(dt.doc_sum), 0) as full_sum,

c.name as from_name, c1.name as to_name

from doc_title dt,

client c,

client c1

where dt.doc_date >= @from_date

and dt.doc_date <= @to_date

and c.client_id = dt.from_id

and c1.client_id = dt.to_id

group by dt.from_id, c.name, dt.to_id, c1.name

go




Последующий пример. Процедура выводит перечень документов и полные имена клиентов:

create procedure LIST_DOC (FROM_DATE date, TO_DATE date)

returns (DOC_ID integer, DOC_NUM varchar(40), DOC_DATE date,

FROM_ID integer, TO_ID integer, FROM_NAME varchar(224),

TO_NAME varchar(224), DOC_SUM numeric(15,4))

as

begin

for select DOC_ID, DOC_NUM, DOC_DATE, FROM_ID, TO_ID, DOC_SUM

from DOC_TITLE

where DOC_DATE >= :FROM_DATE and DOC_DATE <= :TO_DATE

into :DOC_ID, :DOC_NUM, :DOC_DATE, :FROM_ID, :TO_ID, :DOC_SUM

do begin

FROM_NAME = NULL;

TO_NAME = NULL;

execute procedure CLIENT_FULL_NAME (:FROM_ID)

returning_values :FROM_NAME;

execute procedure CLIENT_FULL_NAME (:TO_ID)

returning_values :TO_NAME;

suspend;

end

end

^




На примере перевода данной процедуры покажем один из вариантов того, как можно свести к минимуму количество блокировок на нередко применяемой таблице.

Сделаем для начала вспомогательную таблицу последующего вида:

create table pDoc_List

(

SPID int, —идентификаторподключения

doc_id int,

doc_num varchar(40),

doc_date smalldatetime,

from_id int,

to_id int,

doc_sum DSum,

from_name varchar(224),

to_name varchar(224)

)

go




В данной нам временной таблице мы будем хранить данные, отвечающие нашим аспектам поиска. Для того, чтоб можно было отличить, какому клиенту предусмотрены данные, вводится столбец SPID, в каком мы будем хранить неповторимый идентификатор подключения к БД (@@spid).

Опосля того, как данные скопированы в эту таблицу, мы можем тихо, никому не мешая, обрабатывать их так, как нам захочется. А клиенту (поточнее, серверу приложений) остается лишь их избрать из данной таблицы.

ПРИМЕЧАНИЕ

нужно отметить, что данный метод применим лишь в тех вариантах, где некритично, что меж перечитыванием данных клиентом они могут поменяться




Вот код процедуры, заполняющей эту таблицу:

create proc list_doc @from_date datetime, @to_date datetime

as

declare @from_name varchar(224)

declare @to_name varchar(224)

declare @from_id int

declare @to_id int

declare @doc_id int

declare @doc_num varchar(40)

declare @doc_date datetime

declare @doc_sum dsum

delete from pDoc_List

where SPID = @@spid —очищаем временную таблицу от прошлых данных

—вставляемнужныезаписи

insert into pDoc_List(SPID,

doc_id,

doc_num,

doc_date,

from_id,

to_id,

doc_sum,

from_name,

to_name

)

select @@spid,

doc_id,

doc_num,

doc_date,

from_id,

to_id,

doc_sum,

»,

»

from doc_title

where doc_date >= @from_date and doc_date <= @to_date

—создаем более резвый курсор для обработки записей

declare list_docs insensitive cursor for

select doc_id, from_id, to_id

from pDoc_List

where SPID = @@spid

for read only

open list_docs

fetch next from list_docs

into @doc_id, @from_id, @to_id

while @@fetch_status = 0

begin

select @from_name = », @to_name = »

exec client_full_name @from_id, @from_name output

exec client_full_name @to_id, @to_name output

—заполняем поля, которых нет в главный таблице

update pDoc_List

set from_name = @from_name,

to_name = @to_name

where SPID = @@spid

and doc_id = @doc_id

fetch next from list_docs

into @doc_id, @from_id, @to_id

end

close list_docs

deallocate list_docs

go




SQL-запрос, исполняемый на сервере приложений для передачи данных клиенту:

exec list_doc @from_date = :from_date, @to_date = :to_date

select * from pDoc_List where SPID = @@spid



ПРИМЕЧАНИЕ

При написании ХП следует направить особенное внимание на одновременную работу нескольких юзеров. нужно минимизировать воздействие «томных» (отчетных) процедур на работу клиентов (один из вариантов был показан выше).




Триггеры

здесь ничего сложного нет, нужно лишь держать в голове, что триггеры в MSSQL запускаются лишь опосля деяния (еще есть заместо (instead of), но это лишь в MSSQL2000).

Пример: предотвращение удаления клиента, если есть документы с его ролью (чтоб таковой триггер не конфликтовал с ограничением ссылочной целостности, в MSSQL нужно убрать foreign key с таблицы doc_title на client)

create trigger CLIENT_BEFORE_DELETE for CLIENT

before delete

as

begin

if (exists (select * from DOC_TITLE

where FROM_ID = OLD.CLIENT_ID))

then

exception EX_CLIENT_IN_DOC;

if (exists (select * from DOC_TITLE

where TO_ID = OLD.CLIENT_ID))

then

exception EX_CLIENT_IN_DOC;

end

^




Преобразуется в

create trigger CLIENT_AFTER_DELETE on CLIENT

for delete

as

if (exists (select d.CLIENT_ID from

DOC_TITLE dt, deleted d

where dt.FROM_ID = d.CLIENT_ID))

begin

—чтобы сообщение было видно на клиенте

raiserror (‘Существует запись в документе’, 16, 1)

—необходимо ручками откатить транзакцию

rollback transaction

end

if (exists (select d.CLIENT_ID from

DOC_TITLE dt, deleted d

where dt.TO_ID = d.CLIENT_ID))

begin

raiserror (‘Существует запись в документе’, 16, 1)

rollback transaction

end

go




Модификация сервера приложений.

тут основная часть переработки связана с переходом от IBX (InterBase Express) к ADO (activeX Data Object). Главные вещи, на которые следует направить внимание:

Реализация транзакций на клиенте – в IBX это отдельный компонент, в ADO таковая функциональность предоставляется способами TADOConnection. Еще маленькая рекомендация – аккуратненько подходите к выбору уровня изоляции транзакций (чем меньше уровень изоляции, тем резвее будет работать приложение).

Чтоб клиент работал без переделки с различными источниками данных, нужно, чтоб типы данных полей совпадали. к примеру, в IBX для numeric(15, 4) по дефлоту подставляется TFloatField, а в ADO – TBCDField. Это единственное отличие, которое мне повстречалось при переносе (но это не означает, что их совершенно нет). Неувязка отважилась ручной установкой данного типа поля в TCurrencyField.

Перевод sql-выражений из синтаксиса IB в MSSQL.

Отличия, связанные с различиями структуры БД. к примеру, если будет реализовано каскадное удаление при помощи ХП, то придется реализовывать эту логику снутри сервера, чтоб бросить клиента нетронутым.

В качестве примера приведем перевод одной из процедур сервера приложений:

//Описание того, что делает данная процедура, читайте в статье Игнатьева.

//Код, работающийс IBX

function TrdmDoc.ApplyChanges: WideString;

begin

lock;

try

FLastUpdateErrors := »;

if FState = osInactive then

raise Exception.Create(‘Документнебылсозданлибооткрыт’);

with cdsTitle do

begin

Edit;

FieldByName(‘DOC_SUM’).asCurrency := CalcSum;

Post;

end;

ibtDoc.StartTransaction; //ibtDoc – компоненттранзакции

if FState = osInsert then

begin

if cdsTitle.ChangeCount > 0 then

cdsTitle.ApplyUpdates(-1);

if cdsBody.ChangeCount > 0 then

cdsBody.ApplyUpdates(-1);

end;

if FState = osUpdate then

begin

if cdsBody.ChangeCount > 0 then

cdsBody.ApplyUpdates(-1);

if cdsTitle.ChangeCount > 0 then

cdsTitle.ApplyUpdates(-1);

end;

Result := FLastUpdateErrors;

if Result = » then

ibtDoc.Commit

else

begin

ibtDoc.Rollback;

end;

finally

ibtDoc.Active := False; //DefaultAction = Rollback

unlock;

end;

end;

//Код, работающийс ADO

function TrdmDoc.ApplyChanges: WideString;

begin

lock;

try

FLastUpdateErrors := »;

if FState = osInactive then

raise Exception.Create(‘Документнебылсозданлибооткрыт’);

with cdsTitle do

begin

Edit;

FieldByName(‘DOC_SUM’).asCurrency := CalcSum;

Post;

end;

adcDocs.BeginTrans; //очевидные транзакции задаются на уровне соединения

if FState = osInsert then //а не отдельным компонентом

begin

if cdsTitle.ChangeCount > 0 then

cdsTitle.ApplyUpdates(-1);

if cdsBody.ChangeCount > 0 then

cdsBody.ApplyUpdates(-1);

end;

if FState = osUpdate then

begin

if cdsBody.ChangeCount > 0 then

cdsBody.ApplyUpdates(-1);

if cdsTitle.ChangeCount > 0 then

cdsTitle.ApplyUpdates(-1);

end;

Result := FLastUpdateErrors;

if Result = » then

adcDocs.CommitTrans

else

begin

adcDocs.RollbackTrans;

end;

finally

unlock;

end;

end;




Заключение

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

Данная статья не претендует на полноту освещения данного вопросца, также и создатель при изложении подходов для решения заморочек не претендует на роль «правды в крайней инстанции». тут был изложен только минимум сведений, нужный для решения поставленной задачки, также некие размышления, которые могут посодействовать при решении похожих заморочек.

Все вопросцы, замечания, исправления, дополнения направляйте на kapusto@mail.ru

Желаю выразить признательность Игнатьеву Роману, Павлу Шмакову за советы, критику и напористость.

]]>