ООП IDE Borland C++ Builder Лекция 3_6. Создание приложений для работы с базами данных в сети 1 Содержание 1. Работа с базами данных в сети 2. InterBase - работа на платформе клиент/сервер 3. Доступ к базам данных через ADO 4. Обзор компонентов наборов данных 5. Доступ к InterBase через InterBase Express 6. Доступ к базам данных с помощью компонентов dbExpress 7. Технология MIDAS 2 Управление транзакциями, компонент Database • Управления транзакциями автоматически осуществляются при каждой модификации данных. • Это производится компонентом типа TDatabase, который C++Builder включает автоматически в любое приложение, работающее с базами данных. • Database решает следующие задачи: Создание соединения с удаленным сервером Регистрация пользователя при первом обращении к серверу Создание локальных псевдонимов приложений Управление транзакциями Определение уровня изоляции транзакции (регулирование одновременных транзакций к одним и тем же таблицам) 3 Управление транзакциями, компонент Database • Для сознательного управления транзакциями нужно явным образом включить со страницы Data Access компонент Database в приложение. • Database связывается с компонентами наборов данных Table, Query и другими через имя базы данных, к которой он подключается. • Имя задается в свойстве DatabaseName. • Может быть задан псевдоним базы данных или полный путь к ней. • Если задается база данных, имеющая псевдоним BDE, то свойства AliasName, DriverName и Params можно не задавать. • В противном случае надо задать или свойство AliasName, или свойства DriverName и Params. 4 Управление транзакциями, компонент Database • Значения этих свойств устанавливается в Инспекторе Объектов, или специальным редактором, который вызывается двойным щелчком на Database. • Индикатор Keep inactive connection устанавливает свойство KeepConnection. • После нажатия на ОК введенные установки заполнят значения свойств DatabaseName, AliasName, DriverName, Params и KeepConnection. • Свойство Connected совместно со свойством KeepConnection управляют процессом соединения компонентов с базой данных. • Если KeepConnection равно true, то соединение с базой данных постоянное даже при отсутствии открытых наборов данных. • Если же KeepConnection равно false, то для регистрации на сервере надо устанавливать Connected в true при каждом открытии таблицы. • Свойство Translsolation определяет уровень изоляции транзакции. 5 Управление транзакциями, компонент Database • Значения свойства Translsolation tiDirtyRead Позволяет читать все текущие изменения, проводимые другими транзакциями, до их фиксации. tiReadCommit Позволяет читать только зафиксированные изменения, проводимые другими транзакциями. Это значение принято по умолчанию. После начала транзакции не позволяет читать даже подтвержденные изменения, проводимые другими транзакциями в прочитанных данных. Следовательно, при повторном прочтении на протяжении данной tiRepeatableRead транзакции той же записи будут получены прежние результаты, даже если другие транзакции их уже изменили. 6 Управление транзакциями, компонент Database • Начало транзакции осуществляется методом StartTransaction компонента Database. • При этом начинающаяся транзакция использует текущее значение свойства Translsolation для определения уровня изоляции. • Завершается транзакция методом Commit, фиксирующим ее результаты в базе данных. • Метод Rollback используется для «отката» назад при неудаче - этот метод отменяет все операции с базой данных, выполненные после последнего выполнения метода Commit. 7 Управление транзакциями, компонент Database • • • • • Программа работы с данными должна строиться по следующей схеме: Database1->StartTransaction(); Группа операторов изменения данных (ExecSQL и др.) Проверка результатов: Если успешно – Database1>Commit(); Если неудача – Database1->Rollback(); В случае выполнения Rollback все изменения, сделанные на протяжении транзакции, отменяются. Таким образом, модификации данных, предусмотренные в транзакции, или будут выполнены все, или не будет выполнено ничего. 8 Управление транзакциями, компонент Database • Сокращение числа транзакций возможно за счет команд SQL групповой обработки записей. • Пример: переименовать подразделение во всех записях таблицы «Цех 2» в «Цех 1» можно написать следующий код • Tablel->First(); • while (! Tablel->Eof) • { • if (Tablel->FieldByName("Dep")->AsString == "Цех 2") • { • Tablel->Edit(); Tablel->FieldByName("Dep")->AsString = "Цех 1"; • } • Tablel->Next(); • } • Tablel->First(); 9 Управление транзакциями, компонент Database • Этот код предполагает осуществление стольких транзакций, в скольких записях будут сделаны исправления. • Для каждого исправления будет генерироваться своя команда Update. • Если база данных большая, то это может привести к значительным затратам времени и к проблемам на сервере при регистрации транзакций. • А если во время выполнения цикла возникли какие-то исключительные ситуации, то часть записей будет изменена, а часть - нет. • Последнее можно исправить, поместив перед циклом оператор StartTransaction. 10 • Но неэффективность выполнения останется. Управление транзакциями, компонент Database • Более удачным решением в данном случае является выполнение с помощью Query команды типа: • UPDATE Pers SET Dep='Uex 1" WHERE Dер='Цех 2‘ • Команда будет связана всего с одной транзакцией и гарантирует, что или все необходимые изменения будут сделаны, или не будет сделано ни одного, если в процессе выполнения возникнут какие-то проблемы. • Число транзакций сокращается также за счет кэширования изменений. • Желательно также уменьшать длительность каждой транзакции, поскольку слишком долго выполняемая транзакция может надолго заблокировать ресурсы для других приложений. 11 Управление транзакциями, компонент Database • Если таблица Pers очень большая, то приведенную команду можно разбить на две, выполняемые в пределах отдельных транзакций: UPDATE Pers SET Dер='Цех 1' WHERE (Num BETWEEN 1 AND 10000) and (Dep='Цех 2') UPDATE Pers SET Dep='Цех 1' WHERE (Num > 10000)and(Dep='Цех 2') 12 Управление доступом • Системы управления доступом должны обеспечивать эффективное взаимодействие приложений, работающих одновременно с базой данных. • Исключить наложения результатов работы различных транзакций можно путем блокировки ресурсов, необходимых выполняемой транзакции. • Тогда другие транзакции, обращающиеся к тем же ресурсам, будут ждать, пока данная транзакция не завершится, после чего ресурсы будут освобождены. • C++Builder реализует более оптимальное управление доступом. • Оно предполагает, что большинство обращений к базе данных - это обращения для чтения. 13 Управление доступом • И очень невелика вероятность того, что два приложения одновременно будут изменять одну и ту же запись и одни и те же поля в ней. • Процесс работы приложения C++Builder с базой данных выглядит следующим образом. При выполнении приложения: оно получает копию записи с сервера, позволяет что-то в ней изменить, затем посылает изменения на сервер с помощью команды Update через элемент Where, перечисляющий значения полей, совпадение которых идентифицирует изменяемую запись. 14 Управление доступом • Эти значения берутся из копии записи и, следовательно, не зависят от того, не изменились ли за это время поля реальной записи. • В компонентах Table и Query имеется свойство UpdateMode - режим обновления. • Значения этого свойства и определяют, какие поля будут включены в элемент Where команды Update. 15 Управление доступом Возможные значения UpdateMode : upWhereAll Where включает все поля записи (принято по умолчанию) upWhereChanged Where включает ключевые поля и поля, которые были изменены upWhereKeyOnly Where включает только ключевые поля • upWhereAll наиболее надежен, поскольку распознает запись по значениям всех ее полей, но наиболее трудоемок, поскольку при большом количестве полей элемент Where получается очень большим. • upWhereChanged требует меньших затрат, но он и менее надежен. • Если, пока шло редактирование записи в данной транзакции, другая транзакция изменила в этой записи поля, отличные от измененных данной транзакцией, то фиксироваться результаты будут уже практически для другой записи. • Еще менее надежен вариант upWhereKeyOnly, хотя он наиболее 16 быстрый. Управление доступом Возможные значения UpdateMode : upWhereAll Where включает все поля записи (принято по умолчанию) upWhereChanged Where включает ключевые поля и поля, которые были изменены upWhereKeyOnly Where включает только ключевые поля • upWhereAll наиболее надежен, поскольку распознает запись по значениям всех ее полей, но наиболее трудоемок, поскольку при большом количестве полей элемент Where получается очень большим. • upWhereChanged требует меньших затрат, но он и менее надежен. • Если, пока шло редактирование записи в данной транзакции, другая транзакция изменила в этой записи поля, отличные от измененных данной транзакцией, то фиксироваться результаты будут уже практически для другой записи. • Еще менее надежен вариант upWhereKeyOnly, хотя он наиболее 17 быстрый. Основные свойства компонент Query. • Компонент Query позволяет работать с таблицами, используя операторы SQL. • Большинство свойств и методов Query совпадают с Table. • Дополнительные преимущества Query - возможность формировать запросы на языке SQL. • Query целесообразнее использовать в серверных приложениях Query, а Table - при работе с локальными базами данных . 18 Основные свойства компонент Query. Пример использования Query. • Поместим на форму компоненты Query, DataSource, DBGrid. • В свойстве DataSet компонента DataSource1 зададим Query1, а в свойстве DataSource компонента DBGrid1 зададим DataSource1. • Создали обычную цепочку: набор данных (Query1), источник данных (DataSource1), компонент визуализации и управления данными (DBGrid1). • Основное свойство Query - SQL типа Tstrings - список строк, содержащих запросы SQL. • В процессе проектирования необходимо сформировать в этом свойстве запрос SQL, который указывает, с какой таблицей или таблицами будет проводиться работа. 19 Основные свойства компонент Query. Пример использования Query. • Во время выполнения приложения свойство SQL может формироваться программно методами, обычными для класса TStrings: Clear - очистка, Add - добавление строки и т.д. • Настройку компонента Query в процессе проектирования можно производить вручную • Сначала в свойстве DataBaseName компонента Query надо задать базу данных, с которой будет осуществляться связь из выпадающего списка псевдонимов, или указанием полного пути к каталогу или файлу. • Свойства TableName нет, т.к. таблица, с которой ведется работа, в Query будет указываться в запросах SQL. 20 Основные свойства компонент Query. Пример использования Query. • Поэтому, прежде всего надо занести в свойство SQL запрос, содержащий имя таблицы, с которой нужно работать. • Предупреждение .Прежде, чем начинать детальную настройку компонента Query, надо сформировать в свойстве SQL запрос, в котором указывается таблица и перечисляются параметры, используемые в приложении. • Пока такой запрос в SQL отсутствует, дальнейшая настройке Query невозможно. • Запрос, заносимый в SQL в начале проектирования, носит чисто служебный характер. • В дальнейшем можно его программно заменить на любой другой запрос. 21 Основные свойства компонент Query. Пример использования Query. • Запрос в SQL в начале проектирования может иметь вид: Select * from pers • Система поймет, с какой таблицей будет проводиться работа, и можно будет настроить поля в Query. • Если работа будет проводиться с несколькими таблицами, нужно их указать в запросе: Select * from pers, dep • После формирования запроса можно установить свойстве Active компонента Query в true. • Если все правильно, то в DBGrid1 отобразится информация из запрошенных таблиц. 22 Основные свойства компонент Query. Пример использования Query. • В приложении можно просматривать записи, но нельзя их редактировать. • Это связано с тем, что запрос Select возвращает таблицу только для чтения. • Для включения режима редактирования достаточно установить в компоненте Query1 свойство RequestLive в true. • Это позволяет возвращать как результат запроса изменяемый, «живой» набор данных, вместо таблицы только для чтения. • Точнее, установка RequestLive в true делает попытку вернуть «живой» набор данных. 23 Основные свойства компонент Query. Пример использования Query. • Успешной эта попытка будет только при соблюдении ряда условий, в частности: набор данных формируется обращением только к одной таблице набор данных не упорядочен (в запросе не используется ORDER BY) в наборе данных не используются совокупные характеристики типа Sum, Count и др. набор данных не кэшируется (свойство CashedUpdates равно false) • В примере все эти условия соблюдаются, поэтому установив RequestLive в true можно редактировать данные, удалять записи, вставлять новые записи. 24 Основные свойства компонент Query. • Для управления отображением данных используется Редактор Полей (Field Editor) - вызывается двойным щелчком на Query. В нем можно: • добавить имена получаемых полей (щелчок правой кнопкой мыши и выбор раздела меню Add), • задать заголовки полей, отличающиеся от их имен, • сделать какие-то поля невидимыми (Visible), • не редактируемыми (Readonly), • в логических полях можно задать высвечиваемые слова (да;нет), • задать формат высвечивания чисел, • создать вычисляемые поля, поля просмотра, • задать диапазоны значений и многое другое. 25 Основные свойства компонент Query. • • • • • Динамические запросы и параметры Query Все рассмотренные запросы SQL - это так называемые статические запросы. В них фиксировано все: имена таблиц, поля, константы в выражениях и т.п. SQL допускает также и динамические запросы, использующие параметры. Параметры можно применять вместо имен таблиц, имен полей и их значений. Значения этих параметров передаются извне и тем самым, не изменяя текст самого запроса, можно менять возвращаемый им результат. 26 Основные свойства компонент Query. • • • • Динамические запросы и параметры Query Параметры задаются в запросе с двоеточием, предшествующим имени параметра: :<имя параметра> Например, можно записать в запросе Select элемент WHERE в виде: WHERE Year_b <= :PYear Здесь сравнивается год рождения со значением параметра PYear. Введем в свойство SQL компонента Query запрос, содержащий параметры, например: Select * from pers where (year_b>:PYear) and (dep=:Dep) 27 Основные свойства компонент Query. Динамические запросы и параметры Query • В Инспекторе Объектов при выборе свойства Params, откроется е окно со списком объектов - указанных в запросе параметров PYear и Dep. • В этом списке можно выделять по очереди параметры и в Инспекторе Объектов устанавливать их свойства. Это свойства: DataType тип данных параметра (int, string и т.п.) Name ParamType Value Type - подсвойство Value имя параметра тип параметра (используется при обращении к хранимым на сервере процедурам значение параметра по умолчанию тип значения по умолчанию 28 Основные свойства компонент Query. • • • • Динамические запросы и параметры Query После установки свойства всех параметров, можно использовать их при программировании приложения. Программный доступ к параметрам во время выполнения приложения осуществляется аналогично доступу к полям набора данных. Свойство Params является указателем на массив параметров типа TParam, к элементам которого можно обращаться по индексу через его свойство Items[Word Index]. Последовательность, в которой располагаются параметры в массиве, определяется последовательностью их упоминания в запросе SQL. 29 Динамические запросы и параметры Query. • Значения параметров, как и значения полей, определяются такими свойствами объектов-параметров. Например for (int i = 0; i < Query1->Params->Count; i++) if (Query1->Params->Items[i]->IsNull && Query1->Params->Items[i]>DataType == ftInteger) Query1->Params->Items[i]->AsInteger = -1; • задаст значение «-1» всем целым параметрам, которым до этого не было присвоено значение. • свойство Count - число параметров, • свойство IsNull, равное true, если параметру не задано никакое значение, • свойство DataType, указывающее тип параметра, • свойство AsInteger, дающее доступ к значению параметра как к целому числураметров, как Value, AsString, Aslnteger и т.п. 30 Динамические запросы и параметры Query. • • • • операторы Query1->Params->Items[0]->AsInteger = 1950; Query1->Params->Items[1]->AsString = "Бухгалтерия"; задают значения первому (индекс 0) и второму (индекс 1) параметрам компонента Query1, в свойстве SQL которого записан приведенный ранее оператор Select с параметрами :PYear и :Dep. Поскольку в этом операторе параметр :PYear упоминается первым, го его индекс равен 0. У Params есть еще свойство – ParamValues - представляет собой массив значений параметров типа Variant. В качестве индекса в это свойство передается имя параметра или несколько имен, разделяемых точками с запятой 31 Динамические запросы и параметры Query. операторы • Query1->Params->ParamValues["PYear"] = 1950; • Query1->Params->ParamValues["Dep"] = "Бухгалтерия"; • задают те же значения параметрам, что и приведенные выше. • Преимуществом является то, что при записи этих операторов не надо помнить индексы параметров. • Другим преимуществом свойства ParamValues является возможность задать значения сразу нескольким параметрам. Variant par[] = {1950,"Бухгалтерия“}; Query1->Params->ParamValues["PYear;Dep"] = VarArrayOf(par,1); 32 Динамические запросы и параметры Query. • Другой способ обращения к параметрам - использование метода ParamByName компонента Query. операторы Query1->ParamByName("PYear")->AsInteger = 1950; Query1->ParamByName("Dep")->AsString = "Бухгалтерия"; • задают параметрам с именами PYear и Dep те же значения. • Задание нового значения параметру не обеспечивает влияния на возвращаемый из запроса результат. • Надо повторно выполнить данный запрос, чтобы ощутить изменения. 33 Связывание таблиц • Большинство свойств Query аналогичны свойствам Table. • Объекты полей создаются автоматически для тех полей, которые перечислены в операторе SQL. • Программный доступ к этим полям осуществляется так же с помощью свойства Fields (например, Query1-> Fields[0]) или методом FieldByName (напр. Query1->FieldByName("Dep")). • Можно также создавать объекты полей с помощью Редактора Полей. • В этом случае доступ к объекту поля можно осуществлять также и по его имени (например, Query1Dep). 34 Связывание таблиц • При использовании для Query Редактора Полей надо добавлять в нем все поля, перечисленные в операторе SQL • Предупреждение. Доступ к полям по имени возможен только в случае, если объекты полей были созданы с помощью РП. • Для доступа к значениям полей используются те же их свойства Value, AsString, Aslnteger. • Так же, как в Table, можно осуществлять навигацию по набору данных, устанавливать фильтры, ограничивать вводимые значения полей, кэшировать изменения. • Свойство DataSource компонента Query позволяет строить приложения, содержащие связанные друг с другом таблицы. 35 Связывание таблиц Пример: построить приложение, включающее в себя: таблицу Dep, содержащую список отделов (в поле Dep) и их характеристику, в качестве головной таблицы таблицу персонала Pers, содержащую в поле Dep имя отдела, в котором работает каждый сотрудник. • Нужно, чтобы при выборе записи в таблице Dep в таблице Pers отбирались записи, относящиеся к выбранному отделу. Поместим на форму: компоненты Query1, DataSource1, DBGrid1 соединим их обычной цепочкой: в DBGrid1 свойство DataSource = DataSource1, а в DataSource1 свойство DataSet = Query1. 36 Связывание таблиц • Компонент Query1 настроим на таблицу Dep - установим свойство DatabaseName (например, dbP), а в свойстве SQL напишем оператор Select * from Dep • Установим свойство Active в true и в DBGrid1 должно отобразиться содержимое таблицы Dep. • Создадим другую аналогичную цепочку, перенеся на форму компоненты Query2, DataSource2, DBGrid2, и свяжем ее с таблицей Pers запросом • Select * from Pers в компоненте Query2. • Установим свойство Active компонента Query2 в true и в DBGrid2 отобразится содержимое таблицы Pers. 37 Связывание таблиц • Пока таблицы независимы. • Теперь нужно связать эти таблицы. • Изменим текст запроса в свойстве SQL вспомогательного компонента набора данных Query2 на Select * from Pers where (Dep=:Dep) • В этом запросе указано условие отбора: значение поля Dep должно быть равно параметру :Dep. • Далее в свойстве DataSource компонента Query2 надо сослаться на DataSource1 - источник данных, связанный с таблицей Dep. • Это скажет приложению, что оно должно взять значения параметра :Dep из текущей записи этого источника данных. 38 Связывание таблиц • А поскольку имя параметра совпадает с именем поля в источнике данных, то в качестве значения параметра будет взято текущее значение этого поля. • Таким образом, вспомогательная таблица, запрашиваемая в Query2, оказывается связанной с головной таблицей, запрашиваемой в Query1. • После изменения содержимого свойства SQL свойство Active компонента Query2 сбросится в false. • Нужно установить его в true и запустить приложение. • Увидим, что при перемещении по первой таблице во второй отображаются только те записи, которые относятся к отделу, указанному в текущей записи первой таблицы. 39 Основные методы компонента Query • К основным методам Query можно отнести методы открытия и закрытия соединения с базой данных. • Метод Close закрывает соединение с базой данных, переводя свойство Active в false - метод надо выполнять перед изменением каких-то свойств, влияющих на выполнение запроса или на отображение данных. • Метод Open открывает соединение с базой данных и выполняет запрос, содержащийся в свойстве SQL применим только в случае, если запрос сводится к оператору Select. • Если же запрос содержит другой оператор, например, Update или Insert, то при выполнении Open будет генерироваться исключение EDatabaseError. 40 Основные методы компонента Query • В некоторых случаях приложению требуется получить список имен полей таблицы, связанной с Query. • Это может быть сделано методом GetFieldNames, который загружает список имен полей в любую переменную типа TStrings, передаваемую в него в качестве аргумента. Например, оператор • Query1->GetFieldNames(ComboBox1->Items) ; • загружает в выпадающий список ComboBox1 имена полей таблицы, связанной с Query1. • В дальнейшем будет приведен пример использования этого метода. 41