Как настроить взаимодействие пользовательских приложений и СУБД

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

Немного теории

Появление клиент-серверного подхода обработки данных вызвало спрос на эффективные технологии доступа к данным. Первыми были ODBC, ADO, BDE ... Но их разработчики стремились предоставить максимум сервиса при работе с данными на стороне клиента. Это тянуло за собой громоздкие, сложные компоненты, привязку ПО к рабочей станции клиента, сложные протоколы взаимодействия клиент-сервер. Но вот в начале 2000-х Borland предлагает интерфейс dbExpress. Максимально просто, максимально быстро. Клиентское приложение просто обрабатывает данные выбранные из БД сервера согласно логике приложения и обработав отправляет на сервер. В этом случае интерфейс dbExpress является действительно интерфейсом, а не помесью интерфейса, отображения и управления данными.

При разработке архитектуры dbExpress были решены следующие задачи:

  • уменьшение затрачиваемых ресурсов;
  • получение наибольшей скорости работы;
  • кросс-платформенность;
  • обеспечение простоты разработки драйверов;
  • предоставление разработчику расширенных возможностей управления памятью и трафиком.

Доступ к данным БД сервера осуществляется при помощи небольших драйверов, реализованных в виде динамических библиотек. На нынешний момент для среды разработки RAD Studio 10.4 создано большое количество драйверов серверов баз данных, основные из них:

  • DB2
  • Firebird
  • Informix
  • InterBase
  • MSSQL
  • MySQL
  • Oracle
  • Sqlite
  • ...

В статье Билла Тодда [1] в сжатой форме, но довольно просто и понятно объясняется архитектура provider/resolver. Используется четыре компонента для предоставления данных и их редактирования. Первый компонент — SQLConnection — предназначен для установления соединения между драйвером dbExpress и используемым сервером БД. О деталях применения и настройки компонента немного ниже, а сейчас хотелось бы обратить ваше внимание на очень нужное свойство SQLConnection — управление транзакциями. В dbExpress это реализуется несложно, необходимо добавить вызовы ApplyUpdates перед каждым Commit и CancelUpdates при Rollback. Механизм транзакций обеспечивает железобетонную уверенность в том, что ни один бит данных не будет потерян.

Дальше идут компоненты, которые предоставляют доступ к данным, получаемым оператором SELECT или вызовом хранимой процедуры. Третий компонент — DataSetProvider, и четвертый — ClientDataSet. Когда вы открываете ClientDataSet, он запрашивает данные у DataSetProvider. DataSetProvider открывает компонент, выполняющий запрос или хранимую процедуру, выбирает данные, закрывает этот компонент, и поставляет данные (и необходимые метаданные) компоненту ClientDataSet. ClientDataSet хранит данные в памяти, пока они просматриваются и модифицируются. При добавлении, удалении или обновлении записи, в коде или через пользовательский интерфейс, компонент ClientDataSet запоминает эти операции в памяти. Для обновления базы данных нужно вызвать метод ClientDataSet.ApplyUpdates. ApplyUpdates передает изменения компоненту DataSetProvider. Провайдер стартует транзакцию, затем создает и выполняет операторы SQL, соответствующие произведенным операциям над данными ClientDataSet. Если все операторы SQL были выполнены успешно, провайдер завершает транзакцию по commit; если нет — отменяет транзакцию по rollback. Изменения в базе данных могут не пройти, например, если изменения нарушают правила контроля данных, или если другой пользователь уже модифицировал эти данные определенным образом. При возникновении ошибки транзакция отменяется по rollback, и вызывается событие ClientDataSet OnReconcileError, предоставляя вам возможность обработки ошибок. Пятый компонент, который, по моему мнению, необходим для комфортной работы с интерфейсом dbExpress — DataSource. Компонент DataSource осуществляет связь между компонентом ClientDataSet и компонентами пользовательского отображения данных.

Долгие транзакции, которые присущи более ранним разработкам (ODBC, BDE и т.д.) заставляют сервер БД удерживать блокировки, которые снижают возможности многопользовательской обработки данных и отнимают ресурсы сервера. При архитектуре provider/resolver, транзакция существует только в тот момент, когда применяются обновления. Это существенно снижает требования к ресурсам и уменьшает вероятность блокировок, особенно при большом количестве пользователей сервера БД. Следует также отметить возможность «клонировать» курсор ClientDataSet позволяет просматривать одни и те же данные различными способами, одновременно. Например, можно просматривать одни и те же данные, отсортированные по разным столбцам.

Обычная схема клиент-сервер — приложения разрабатываются таким образом, чтобы выбирать небольшой объем данных для минимизации сетевого трафика и загрузки сервера БД. Даже если нужно работать с необычно большим количеством записей, то 10 тысяч записей, каждая по 100 байт, занимают 1 мегабайт памяти. В случаях, когда объем данных действительно большой, компоненты ClientDataSet и DataSetProvider имеют свойства и события, которые позволяют выбирать часть записей, редактировать их, удалять из памяти и затем получать новую порцию записей.

Несложный пример

Начинаем новый проект с одной формой. На форму необходимо установить SQLConnector из вкладки компонентов dbExpress. Это первый элемент «цепочки» подключения. Основная задача этого компонента подключится к СУБД. Необходимые установки в инспекторе объектов: Driver — MySQL, далее идут строки в окне Params.

  • HostName — имя хоста или его IP,для тестовой задачи на своём компьютере — localhost.
  • Database — имя имеющейся БД в среде MySQL.
  • User_Name — пользователь MySQL.
  • Password — его пароль.

Наиболее удобный вариант — это хранить параметры коннекта в ini-файле. При этом нужно соответствующим образом запрограммировать чтение параметров из ini-файла в SQLConnection.Params при старте приложения.

Снимите галочку LoginPrompt, что бы исключить ввод логина/пароля за каждым коннектом

Компилируем. В папку <Мой проект>\ Win32\ Debug \ необходимо скопировать libmySQL.dll из \bin СУБД MySQL в каталог с exe-модулем, либо libmySQL.dll должен лежать в \system32
Далее потребуются компоненты:

  • SQLQuery — вкладка dbExpress. TSQLQuery предназначен для выполнения команды SQL на сервере баз данных. SQLQuery— это однонаправленный набор данных. В отличие от других наборов данных, однонаправленные наборы данных не буферизируют несколько записей в памяти. Из-за этого необходимо ориентироваться только на методы First и Next. В инспекторе объектов выставляем SQLConnection в SQLConnection1.
  • DataSetProvider — вкладка DataAccess. В инспекторе объектов выставляем DataSet — SQLQuery1.
  • ClientDataSet— вкладка DataAccess. В инспекторе объектов выставляем ProviderName — DataSetProvider1.
  • DataSource— вкладка DataAccess. Ещё несколько слов о DataSource. Он задает набор данных, для которого компонент источника данных служит в качестве канала для элементов управления поддержки различных наборов данных. Для DataSet задаётся имя существующего компонента набора данных во время разработки или выполнения. Изменяя значение DataSet, приложение может эффективно использовать одни и те же элементы управления с учетом данных для отображения и редактирования в разных наборах данных. В тестовом примере две цепочки подключённых к одному SQLConnection. Для отображения результатов тестирования интерфейса на форме разместим DBGrid.

Создаём базу данных в среде MySQL. Это табличка из 5 записей с полями id и name. Source-файл есть на GitHub, как и исходники всего теста. Заполняем Params для SQLConnection1.

  • HostName — localhost.
  • Database — testconnect.
  • User_Name — пользователь MySQL.
  • Password — пароль.
  • Снимаем галочку LoginPrompt.

Обработчик кнопки «Get Data» выгляядит следующим образом:

void __fastcall TForm1::Button1Click(TObject *Sender){
// Кнопка "Получить данные"
UnicodeString S;
int i;
//
	S = "select * from component";
	DBGrid1->DataSource = DataSource1;
   DBGrid1->DataSource->DataSet = ClientDataSet1;
	//
	ClientDataSet1->Close();
	SQLQuery1->SQL->Clear();
	SQLQuery1->SQL->Add(S);
	ClientDataSet1->Open();
	//
}
И несколько советов по эксплуатации. Для получения данных (SELECT) и обновления данных (INSERT, UPDETE, DELETE) целесообразно написать такие методы:
Для SELECT (т. е. выборка, не изменяя данных в БД)
int QuerySQL(UnicodeString S, int NumChain) {  
// Возвращает количество прочитанных записей
// S – строка SQL-запроса
// NumChain – № цепочки, по которой идёт выборка данных, их 
// желательно иметь несколько, т.к. данные могут выбираться из разных
// таблиц или представлений.
…
switch (NumChain) {
		case 1:
			DataModule1->ClientDataSet1->Close();
			DataModule1->SQLQuery1->SQL->Clear();
			DataModule1->SQLQuery1->SQL->Add(S);
			break; 
…

Метод обновления данных:

void ExecSQL(UnicodeString S, int NumChain) {
//	S – строка SQL-запроса 
// NumChain – № цепочки. Цепочки целесообразно использовать
// в методе, если есть несколько SQLConnection подключённых к 
// разным БД (и/или не только MySQL)  
…
switch (NumChain) {
		case 1:
DataModule1->SQLQuery1->SQL->Clear();
			DataModule1->SQLQuery1->SQL->Add(S);
			DataModule1->SQLQuery1->ExecSQL();
break;
…

Описание такого подхода к обработке данных имеет много преимуществ и, с моей точки зрения, одно из главных — обработка их на сервере. Пускай сервер работает, он в разы производительнее, клиенту просто выгружается результат, даже если у вас 200-2000 рабочих станций. И не нужно знать SQL в совершенстве, достаточно для, например, фильтра

S = "select * from component where id=4";

или сортировки

S = "select * from component order by name";

Волшебный TField

Объекты класса TField являются свойством объекта TDataSet. Свойство Fields объекта типа TDataSet позволяет обращаться к отдельным полям набора данных. Свойство Fields является массивом или набором объектов TField, динамически создающимся во время выполнения приложения. Элементы массива соответствуют колонкам таблицы. Объект TField не делает никаких предположений относительно типов данных, с которыми он связан. Он имеет несколько свойств, позволяющих установить или вернуть обратно значения поля, например, AsString, AsBoolean, AsFloat, AsInteger.

Для примера:

UnicodeString S;
TField *f;
int NumID;
// Здесь необходимо значение id в виде строки для компоновки 
// запроса S
f=DataModule1->ClientDataSet1->FieldByName("id");
S = "select * from component where id="+f->AsString;
// А здесь в виде обычного инта
NumID = f->AsInteger;

И напоследок. Описанная технология имеет многолетний период эксплуатации и ещё ни разу не подводила, отрабатывала на 100%. В мультике «80 дней вокруг света» Филеас Фогг всё время повторял гениальную фразу «Используй то, что под рукою и не ищи себе другое». Используйте dbExpress и будет вам счастье.

Использованные источники

Статья Bill Todd «Migrating Borland® Database Engine Applications to dbExpress».

Послед Б.С. Borland® C++ Builder 6. «Разработка приложений баз данных».

Ссылка на GitHub.

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Неужели этому до сих пор учат студентов в технических вузах?

«Этому» учат, очевидно, в Кембридже и Оксворде :) т.к. приведу пример задания 3(!) курса ПРОФИЛЬНОГО киевского ВУЗа по WPF
«Cтворити WPF-проект. Використати для вікна малюнок як фон.
Розмістити у вікні прямокутник і зафарбувати його за допомогою пензля VisualBrush.»
Грустно как-то...

Вот хороший новый вброс для всех присутствующих
dou.ua/forums/topic/31052

Бо конкретне технічне питання, про котре можна ще тиждень дискутувати з усіх боків.
Нажаль, в мене з СУБД нема нормального досвіду, інакше б приєднався)

Це, як на мене — зайве на GitHub
github.com/...​/ConnectToMySQL/__history

github.com/...​ConnectToMySQL/__astcache

а це взагалі зайве-зайве, усі ті білд-мусорні ошмьотки, .pch, бінарник, obj
github.com/...​onnectToMySQL/Win32/Debug

Капця вразила ціль! Я можу придумувати багато пояснень та виправдань, але Ваші зауваження вважаю слушними, зроблю висновки.

TForm1::Button1Click

ухх, я аж на дату поста подивився, думав, що це якась стаття з дня заснування ДОУ
ан нє — це попаданці з минулого в наші дні, ласкаво просимо :-)

YO! А за станом на зараз обробник події натискання Button (якщо це Button1) має інший синтаксис? Типу «Future_ahead::NewGalaxy» чи з C# «private void NewGalaxy _Click(object sender, EventArgs e)»? За запрошення у ваше майбутнє дякую, але не дуже хочеться... Світ «ваших днів» якийсь паралельний чи ортогональний, а мо віртуальний. А минуле вас наздожене у світі реальному, і хто тоді буде попаданцем?

YO! А за станом на зараз обробник події натискання Button (якщо це Button1) має інший синтаксис?

1. це просто діла давно минувших днів — Visual Basic, Delphi, Builder. На жаль, хоч вони і були хороші (після 20+ років тільки от недавно Amazon Honeycode випустили щось схоже), але для них мінімум місця серед сучасних методів розробки

2. Хорошою практикою було перейменовувати TForm1 на щось типу frmMain чи щось інше по смислу, аналогічно і Button1 на щось типу btnLoad чи щось таке, щоб не треба було гадати що ті 1-2-3-4 і т.д. цифри означають.

1. Builder(тепер «Embarcadero») — це просто реалізація СРР. Доволі вдала та зручна, звісно з моєї точки зору. Дехто тяжіє до MS чи Qt. Існує доволі великий пласт ПЗ, який не можна зробити на Python, PHP, SQL. DjangoOS та driver_video_sys.js виглядає трохи смішно. «мінімум місця серед сучасних методів розробки»? А які сучасні? Samsung-Tizen це не сучасно? Є доволі значна кількість великих систем, які не можна підключати до інтернету, хмарні технології не рятують, AI не допомагає, все треба робити руцями.
2. Я вже писав у одному із коментарів, що однією із ідей написання цього прикладу була простота реалізації. Реалізації з нуля до простого модулю, який бере дані з таблиці однієї СУБД і пише в таблицю іншої. Показано, що з допомогою «Embarcadero dbExpress» це можна зробити швидко та ефективно. Не враховані зауваження Oleksii Kliukin про prepared statements, блоки try- catch, вибірку даних підключення з .ini-файлу, та й може ваше зауваження про frmMain. Хоча скрізь прописані коментарі таким чином, щоб усе було одразу зрозуміло. Російською — «будь проще и к тебе потянутся люди»... Я цим прислів’ям користуюсь для простоти пояснень.

Borland® C++ Builder 6.

Я думал это уже давно кануло в лету и никто и не помнит об этом

Что сейчас востребовано — скрумы, джанги, Yii, типескипт и т.д. ... Народ ломится изучать новую агилу или котлин. Мейнстим — YO! А на чём вся эта хрень работает?
ASM, CPP, ну, может быть PYTHON (Mozilla и т.д.) Так вот, возвращаясь к десктопным пользовательским приложениям (такие ещё существуют и неплохо себя чувствуют) становится вопрос — на чём писать? Из адекватных (это там, где ненужно настраивать 148 параметров) — MS, Embarcadero, Qt. Остальные завязаны на внешние фреймворки, догрузки модулей и всякую хрень, что при вводе в эксплуатацию и при самой эксплуатации вылазит боком. Я уже не говорю о разработке. Сейчас Embarcadero (девичья — Borland) идёт хорошо, 10.4 — Rio. Вспомним молодость и удивимся вкусным прелестям. Т-34 тогда — надёжно и быстро и Т-80 сейчас + комфортно и мощно. Это маленькая политинформация о немодном продукте, который никогда тебя не кинет, проверенная десятилетиями. А о кидаловах больших корпораций — разработчиков инструментария — у гугля страниц не хватит.

Т-34 тогда — надёжно
Т-80 сейчас + комфортно

перестаньте приводить примеры на том, в чем совершенно не разбираетесь
при чем SQL-запросов это тоже касается

Как мне нравятся такие комментарии! Сидит эдакий Очень Умный Мэтр (условно) и сочиняет Трактатъ о влиянии на производительность 157 бита в МD5 серверов IBM/CISCO-VX/PX/MC-148-23i эдак страниц на 200-250 и вся Академическая тусовка в восторге. И пригодился этот мануал двум инженерам, одному из США, другому из Австралии, они в восторге. Но трабл в том, что этот сервер был выпущен в 3-х экземплярах. А у Юзверя из Мысайловки (условно) в хозяйстве MSSQL/MySQL/Firebird/Oracle, что с этим делать — х.з., и ему глубоко наср.. на 157 бит.
Теперь об SQL. В чём я точно разбираюсь, так это в SQL и танках.
PS. «ПЕРЕСТАНЬТЕ ПРИВОДИТЬ» и «НЕ РАЗБИРАЕТЕСЬ» — это у вас должность такая? Мне искренне жаль ваших подчинённых. «Я начальник, ты — дурак!», это работает в пределах хижинки-кАнторки Очень Умного Мэтра, не в нете.

аж молодость перед глазами пробежала, со времен школы не видел

Borland® C++

а это на минуточку 2003й год

Что-то мне повеяло ароматом лабораторных из техникума....
А вздрогнул

А техникум в Козятині? Если да, то я записываюсь тады на 1-й курс, может зубром стану... Да и вздрагивать не буду...

Пожалуйста, не пишите запросы вот так:

f=DataModule1->ClientDataSet1->FieldByName("id");
S = "select * from component where id=”+f->AsString;

Писать SQL запросы вот такой конкатенацией строк очень опасно, однажды поле id окажется не числовым, а строковым, и в него напишут «’1′; TRUNCATE TABLE component; ». И будет как в xkcd.com/327 :-)

Используйте prepared statements, хранимые процедуры или на худой конец возможности драйвера по экранированию параметров запроса вместо вставки параметров «вручную».

1. Спасибо за комментарий.
2. Основной идеей поста была мысль, что dbExpress — это просто. В оригинале статья и называлась «Embarcadero dbExpress — эффективно зацепится за данные». Но модераторы DOU решили, что такой заголовок неправильный. Ну и ладно. Не раз рассматривая примеры с того же StackOverflow исходник в несколько сотен строк, необходимое, например, обращение к API глубоко-глубоко. Остальное — блоки try- catch на все случаи жизни — обрыв сети, выключение электричества, землетрясение в Того... Да, я согласен, использование prepared statements и try- catch в рабочих системах необходимо, также, как и пропущенное обращение к .ini — файлу. Но это убило бы простоту примера. А простота — это 3-4 строчки кода, которые выполняют всю основную работу, Остальное — необходимая обвеска. Это ведь не учебник по программированию. Но выводы я сделаю.
3. Здесь прозвучало «хранимые процедуры». Как-то я участвовал в дискуссии по применению/неприменению, использованию/неиспользованию хранимых процедур. Народа собралось довольно много, потом подтянулись ещё... Скажу сразу — дискуссия окончилась ничем, каждый остался при своём мнении. Я категорический противник хранимых процедур, функций, если только это не касается высоконагруженных систем. Вообще, мне кажется, что прогеры делятся на касты — те, которые делают правильные приложения строго по ТЗ (оно обязательно есть😊, и «правильное»), придерживаются сроков выполнения тасков, ходят в фитнес-клуб и работают в APPLE или на худой конец в GOOGLE, и на остальных. А у остальных, например, есть СУБД 5-7 лет отроду и человек 12 её сопровождали и «допиливали». Из документации — обрывки маленьких .txt файлов. И когда приложение выдаёт «2 + 2 = 2,5», то тогда очень интересно просмотреть поток мыслей прогера у монитора — ошибка в запросе — сломался компилятор — пора в отпуск — да ну её к чёрту, эту работу... Это немного утрировано, но смысл такой. Хранимые процедуры (обобщённо, это и функции, и триггеры) — это мина, которая неизвестно, когда, как и где рванёт. Допишите немного кода в вашем приложении, пожалуйста.

Но это убило бы простоту примера. А простота — это 3-4 строчки кода, которые выполняют всю основную работу, Остальное — необходимая обвеска. Это ведь не учебник по программированию.

Это та самая «простота, которая хуже воровства». Эти 3-4 строчки кода нужно еще уметь написать правильно, чтобы не навредить.

Остальное — блоки try- catch на все случаи жизни — обрыв сети, выключение электричества, землетрясение в Того...

Откуда вы взяли блоки try...catch? Я писал про экранирование средствами драйвера, который сам либо создает prepared statement, либо хотя бы экранизирует данные в запросе. Блоки try..catch в коде приложения вам не помогут, потому что на данные в СУБД они никак не повлияют.

Здесь прозвучало «хранимые процедуры». Как-то я участвовал в дискуссии по применению/неприменению, использованию/неиспользованию хранимых процедур. Народа собралось довольно много, потом подтянулись ещё... Скажу сразу — дискуссия окончилась ничем, каждый остался при своём мнении. Я категорический противник хранимых процедур, функций, если только это не касается высоконагруженных систем.

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

Вообще, мне кажется, что прогеры делятся на касты — те, которые делают правильные приложения строго по ТЗ (оно обязательно есть😊, и «правильное»), придерживаются сроков выполнения тасков, ходят в фитнес-клуб и работают в APPLE или на худой конец в GOOGLE, и на остальных. А у остальных, например, есть СУБД 5-7 лет отроду и человек 12 её сопровождали и «допиливали». Из документации — обрывки маленьких .txt файлов. И когда приложение выдаёт «2 + 2 = 2,5», то тогда очень интересно просмотреть поток мыслей прогера у монитора — ошибка в запросе — сломался компилятор — пора в отпуск — да ну её к чёрту, эту работу...

Программисты по-моему опыту делятся на тех, кто хочет учиться и развиваться, и тех, кому «и так сойдет». Первые пишут документацию и переписывают старый код, если он тормозит развитие проекта, вторые следуют ритуалам, написанным на клочках документации и передаваемых устно от увольняющихся коллег к новым, работают десятилетия, обучаясь год и потом повторяя его десять раз, и считают себя экспертами во всем (для них даже есть слово на английском, Expert Beginner: daedtech.com/...​e-of-the-expert-beginner). Выбор каждый делает сам.

Спасибо за ссылку на Expert Beginner, интересно. Развиваться — согласен, а работать? Мир не вращается только вокруг Microsoft Azure, AI, ML, есть ещё огромный пласт ПО, которому лет 300 и эти проги ДОЛЖНЫ работать. Мне в этом плане нравится работа Р. Мартина «Чистый код», такая, написанная с воспитательно-поучительной целью. Не нравится модуль — перепиши! Но! А кто за это заплатит? То, что прогер совершенствуется это хорошо — для него, для прогера, а бизнесюк понимает, что прогер совершенствуется за ЕГО бабки. Потом свалит, где больше предложат.И возвращаясь к облакам и современным и сверхсовременным технологиям. Есть сети, которые нельзя принципиально включать в нет. Я даже знаю одну не бедную страну, где облако строят для её управленческих организаций. И пользователи на раб. столе держат 2 компа, один для нета, другой для работы. (Хотя это тоже нельзя) И прогами во внутренней сети необходимо заниматься... Даже не экспертам. А хранимые процедуры — да действительно, согласен, это инструмент, шуруповёрт, тяжёлый, пока не хряпнул по затылку. Сыграйте в «Сапёра» 100×100. Каковы шансы?

Коментар порушує правила спільноти і видалений модераторами.

Підписатись на коментарі