Подавляющее большинство приложений в той или иной степени используют в процессе работы базы данных. При разработке таких приложений весьма полезными оказываются инструментальные средства, упрощающие взаимодействие с различными СУБД. Одним из таких инструментов является библиотека MySQL++, которая позволяет эффективно организовать доступ к базам данных MySQL из программ, написанных на языке C++. Я попытаюсь подробно разобрать функциональные возможности этой библиотеки, её достоинства и недостатки. Начну с общих характеристик MySQL++.
MySQL++ - это специализированная библиотека так называемых "обёрточных" (wrapper) методов для прикладного программного интерфейса C API для СУБД MySQL. Главная цель этой библиотеки - сделать работу с SQL-запросами такой же простой, как работа с STL-контейнерами.
Самую последнюю версию библиотеки MySQL++ можно найти на официальном Web-сайте. Тем не менее, чтобы избежать дополнительных сложностей, лучше обратиться к репозиторию пакетов для вашего дистрибутива.
В 1998 г. Кевин Аткинсон [Kevin Atkinson] начал разработку библиотеки, которая по его первоначальному замыслу обеспечивала бы выполнение SQL-запросов и обработку их результатов без привязки к какой-либо конкретной СУБД. Это было отражено даже в оригинальном названии - SQL++. Все версии, предшествующие 1.0, являются плодом индивидуальной работы Кевина.
В 1999 г. библиотекой занялась компания MySQL AB. Сначала некоторую работу проделал Майкл "Монти" Видениус [Michael "Monty" Widenius], затем он передал полномочия другому сотруднику MySQL AB Синише Миливоевичу [Sinisa Milivojevic]. Были выпущены версии 1.0 и 1.1, после чего Аткинсон официально передал все функции сопровождения библиотеки в руки Миливоевича и полностью устранился от какого бы то ни было участия в данном проекте. Миливоевич довёл библиотеку до версии 1.7.9, которая была выпущена в середине 2001 г. К этому моменту стала очевидной невозможность реализации универсальной библиотеки SQL-запросов, независимой от конкретных реализаций СУБД. Ориентация на MySQL стала неизбежной.
После выпуска версии 1.7.9 в работе над MySQL наступил период некоторого застоя, который продолжался три года, до августа 2004 г., когда ситуацию под контроль взял Уоррен Янг [Warren Young]. Уоррен сразу же выпустил версию 1.7.10, устранил все проблемы с компиляцией при использовании GCC, исправил большое количество ошибок, добавил новые возможности. В общем, как говорится, "процесс пошёл"...
В данной статье я рассматриваю версию 3.0.9 библиотеки MySQL++. На официальном Web-сайте эта версия объявлена как "последняя стабильная".
2. Краткое описание основных объектов (соединение, запрос, результаты)
MySQL++ обеспечивает поддержку большинства разнообразных способов и приёмов работы с базами данных. И всё же, несмотря на это разнообразие, можно выделить обобщённую схему использования API-интерфейса, предназначенного для доступа к базам данных:
- создание (открытие) соединения с базой данных,
- формирование и выполнение запроса,
- при успешном выполнении запроса - обработка итогового набора (result set) - итеративный последовательный проход по записям этого набора,
- если выполнение запроса завершилось неудачно, то необходимо обеспечить обработку ошибок (исключений).
Каждому из перечисленных выше этапов соответствует класс или иерархия классов библиотеки MySQL++. Рассмотрим их немного подробнее.
2.1. Объект Connection (соединение)
Объект Connection управляет соединением с сервером MySQL. Для того чтобы выполнять какие-либо операции в базе данных, необходим по меньшей мере один такой объект. В приложении использование всех прочих MySQL++-объектов зависит (хотя и не всегда непосредственно) от экземпляра Connection, поэтому пока в вашей программе применяются объекты библиотеки MySQL++, должен существовать и объект Connection.
В СУБД MySQL допускается использование нескольких различных типов соединений между клиентом и сервером: сокеты TCP/IP, сокеты Unix-домена, именованные программные каналы. Базовый класс Connection поддерживает все эти типы соединений. Какой именно тип соединения необходим в каждом конкретном случае, вы определяете с помощью параметров, передаваемых в метод Connection::connect(). Но если вы заранее решили, что ваше приложение будет работать только с одним типом соединений, то вашему вниманию предлагаются специализированные подклассы с упрощёнными интерфейсами. Например, если в программе предполагаются обращения исключительно к сетевому серверу баз данных, то вы можете воспользоваться подклассом TCPConnection.
Чаще всего SQL-запросы создаются с использованием объекта Query, инициализируемого объектом Connection.
Query работает практически аналогично потоку вывода в стандартном C++, поэтому вы можете записывать в него данные так же, как в std::cout или std::ostringstream. Это наиболее близкий стилю языка C++ способ, используемый MySQL++ для компоновки строк запросов. В библиотеку включены манипуляторы потока с контролем типов данных, что существенно упрощает создание синтаксически корректных SQL-команд.
Ещё одной функциональной особенностью Query являются запросы-шаблоны (Template Queries), которые в определённой степени напоминают функцию языка C printf: формируется строка запроса с включаемыми в неё тэгами, обозначающими места вставки переменных элементов данных. Это удобно в тех случаях, когда в программе используются многочисленные запросы с одинаковой структурой - определив один шаблон, вы можете применять его многократно в различных частях своего приложения.
Третий метод создания запросов - использование объекта Query совместно со специализированными структурами SSQLS (Specialized SQL Structures), которые позволяют создавать структуры C++, являющиеся точным отображением схем конкретных баз данных. Это даёт объекту Query информацию, необходимую для формирования обобщённых SQL-запросов.
2.3. Наборы результатов (Result Sets)
Данные полей в наборе результатов запроса сохраняются в специальном классе с именем String (аналог стандартного std::string). Этот класс предоставляет операторы для автоматизированного преобразования объектов наборов результатов в любой базовый тип C/C++. Кроме того, MySQL++ определяет классы, такие как DateTime, которые вы можете инициализировать данными SQL-типа DATETIME. Для этих автоматических преобразований осуществляется контроль их корректности, и при ошибках конвертации выставляется соответствующий флаг-предупреждение или генерируется исключение (в зависимости от настроек библиотеки).
Для предоставления результатов выполнения SQL-команд в библиотеке MySQL++ реализованы следующие подходы.
2.3.1. Команды, не возвращающие данные
Не все команды SQL возвращают данные. Пример такой команды CREATE TABLE. Для подобных команд имеется специальный тип результата (SimpleResult), который возвращает только состояние после выполнения команды: успешное завершение выполнения команды, количество строк, на которые подействовала команда (если подразумевалось какое-либо воздействие), и т.п.
2.3.2. Запросы, возвращающие данные: структуры данных MySQL++
Самый простой способ получения набора результатов - использование метода Query::store(). Этот метод возвращает объект StoreQueryResult, являющийся производным от std::vector<mysqlpp::Row>, представляющий собой контейнер с произвольным доступом, сформированный из Row-строк. В свою очередь, каждый объект Row является аналогом вектора (std::vector) строк (тип String), по одному объекту на каждое поле в наборе результатов. Таким образом, вы можете рассматривать StoreQueryResult как двумерный массив, т.е. чтобы получить пятое поле из второй строки, можно просто написать result[1][4]. К полям можно обращаться и по их именам, поэтому возможна и такая форма записи: result[1]["price"].
Несколько менее удобным способом работы с набором результатов является применение метода Query::use(), возвращающего объект UseQueryResult. Этот класс функционирует подобно стандартному STL-итератору. В этом случае произвольного доступа к данным уже не получится - вы последовательно проходите по строкам набора результатов с ограничением по направлению: только от начала к концу. Нет возможности возвращаться к уже пройденным строкам, и вы не знаете, сколько строк в данном наборе до тех пор, пока не достигнете последней строки. В качестве компенсации за эти неудобства вы получаете более рациональное использование оперативной памяти, поскольку нет необходимости загружать в память весь набор полностью. Это особенно важно, если приходится работать с чрезвычайно большими наборами результатов.
2.3.3. Запросы, возвращающие данные: специализированные структуры SSQLS
Доступ к результатам запросов посредством структур данных библиотеки MySQL++ представляет достаточно низкий уровень абстракции - это лучше, чем применение MySQL C API-интерфейса, но не намного. Приблизить логику решения к предметной области задачи можно с помощью специализированных структур SSQLS (Specialized SQL Structures). Эти SSQLS-объекты позволяют определять структуры языка C++, которые соответствуют структурам таблиц в схеме конкретной базы данных. Кроме того, гораздо проще состыковать SSQLS-объекты со стандартными STL-контейнерами (а следовательно, и с алгоритмами).
Преимущество этого способа заключается в том, что в программе потребуется включение минимального объёма SQL-кода. Можно выполнить запрос и получить результат в виде структур данных языка С++, доступ к которым не отличается от доступа к любым другим структурам. Доступ к полученным данным можно организовать через объект Row или же обратиться к методам библиотеки, чтобы "сбросить" результаты в STL-контейнер - с последовательным, произвольным или ассоциативным доступом - выбор за вами.
Рассмотрим следующий фрагмент кода:
vector<stock> result; query << "SELECT * FROM stock"; query.storein( result ); for( vector<stock>::iterator i = result.begin(); i != result.end(); i++ ) cout << "Цена: " << i->price << endl; |
Практически "чистый" код на C++, без излишеств.
Если по каким-либо причинам создание SSQLS-объектов, соответствующих структурам таблиц баз данных, нежелательно или невозможно, то вы можете использовать объект Row, и приведённый выше фрагмент кода теперь будет выглядеть так:
vector<mysqlpp::Row> result; query << "SELECT * FROM stock"; query.storein( result ); for( vector<mysqlpp::Row>::iterator i = result.begin(); i != result.end(); i++ ) cout << "Цена: " << i->at("price") << endl; |
Различия невелики. Первый фрагмент выглядит более лаконично, но на внутреннюю логику это не влияет.
По умолчанию при возникновении ошибок библиотека генерирует исключения (exceptions). При необходимости вы можете настроить вместо генерации исключений установку специального флага ошибки (error flag), но помните, что исключения предоставляют более подробную информацию. В пределах одного блока try-catch вы можете выявлять различные типы ошибок.
3. Простой пример использования MySQL++
В следующем примере показано создание (открытие) соединения с базой данных, выполнение простого запроса и вывод полученных результатов - всё, о чём мы говорили до сих пор.
#include <iostream> #include <iomanip> #include <mysql++.h> using namespace std; int main( int argc, char *argv[] ) { // установление соединения с тестовой базой данных mysqlpp::Connection con( false ); if( con.connect( "test_db", "localhost", "tdb_user", "tdb_password" ) ) { // выполнение запроса и вывод полученных результатов mysqlpp::Query query = con.query( "select name from dvd" ); if( mysqlpp::StoreQueryResult res = query.store() ) { cout << "Коллекция DVD: " << endl; for( size_t i = 0; i < res.num_rows(); i++ ) cout << res[i][0] << endl; } else { cerr << "Ошибка при получении списка DVD: " << query.error() << endl; return -1; } return 0; } else { cerr << "Ошибка при установлении соединения с БД: " << con.error << endl; return -1; } } |
Для обеспечения работы примера необходимо создать базу данных MySQL с именем test_db, содержащую таблицу с именем dvd. Эта таблица может иметь, например, следующую структуру:
name VARCHAR(50) genre TEXT(70) price REAL bought DATE |
После того как тестовая база данных test_db создана, администратор MySQL должен создать пользователя с правами работы в ней. Имя и пароль для этого пользователя уже были указаны в приведённом выше исходном коде примера.
CREATE USER tdb_user@localhost IDENTIFIEDBY 'tdb_password'; GRANT ALL PRIVILEGES ON test_db.* TO tdb_user@localhost; |
После этого пользователь tdb_user, подключившийся к базе данных test_db на локальном хосте, может создать в ней ранее описанную таблицу dvd и любые другие таблицы для тестирования, вносить изменения в эти таблицы, удалять их и производить прочие операции.
Что касается самой программы, то она запрашивает только поле имён (name) из таблицы dvd, и из полученного набора результатов res последовательно выводит все строки (названия DVD). Пример очень простой, но в нём проиллюстрированы три из четырёх этапов, упомянутых в начале данной статьи. О четвёртом этапе - обработке исключений - речь пойдёт в одной из следующих статей цикла.
Даже после первого, достаточно беглого взгляда на функциональные свойства и характеристики библиотеки MySQL++ становится понятно, что она способна оказать существенную помощь разработчикам приложений, взаимодействующих с серверами СУБД MySQL. Библиотека легко адаптируется к потребностям программиста, не содержит ненужных излишеств и без затруднений "вписывается" в исходный код на языке C++.
В данной статье рассматривались общие характеристики библиотеки MySQL++, её функциональные возможности. В качестве иллюстрации был приведён простой пример исходного кода с применением объектов MySQL++. Следующая статья цикла будет посвящена настройке и адаптации библиотеки MySQL++ в различных проектах. В третьей статье будет подробно описано выполнение SQL-запросов и обработка их результатов. Тема четвёртой статьи - обработка ошибок и использование транзакций. В пятой статье мы рассмотрим различные типы данных и работу с ними. Специализированным формам запросов уделяется особое внимание в шестой статье . В заключительной, седьмой статье цикла будет продемонстрировано практическое применение библиотеки MySQL++ в многопоточных приложениях.
Средний показатель рейтинга (40 голоса)
Нет комментариев |
Войти or зарегистрироваться чтобы оставить комментарий.
Внимание: HTML элементы не поддерживаются в комментариях.