Модуль SQL, в библиотеке Qt, предоставляет независимый от типа платформы и базы данных интерфейс, для доступа к базам данных SQL, и набор классов, обеспечивающих взаимодействие пользовательского интерфейса с базами данных.
Глава начинается с демонстрационного примера, который показывает, как установить соединение с базой данных и как выполнить произвольный SQL-код. Во втором и третьем разделах мы подробнее остановимся на том, как предоставить пользователю возможность просматривать и изменять наборы данных, используя QDataTable -- для просмотра данных в табличном виде, и QSqlForm -- в виде формы.
Прежде чем выполнить запрос к базе данных, для начала необходимо установить с ней соединение. Как правило, установление соединения с базой данных выполняется в виде отдельной функции, которую приложение вызывает на запуске, например:
bool createConnection()
{
QSqlDatabase *db = QSqlDatabase::addDatabase("QOCI8");
db->setHostName("mozart.konkordia.edu");
db->setDatabaseName("musicdb");
db->setUserName("gbatstone");
db->setPassword("T17aV44");
if (!db->open()) {
db->lastError().showMessage();
return false;
}
return true;
}
Первым делом, вызовом QSqlDatabase::addDatabase(), создается экземпляр
класса QSqlDatabase. Аргумент функции
определяет драйвер базы данных, используемый для доступа к ней. В
данном случае -- это драйвер Oracle. Коммерческая версия Qt включает в
себя следующий набор драйверов: QODBC3 (ODBC), QOCI8 (Oracle), QTDS7
(Sybase Adaptive Server), QPSQL7 (PostgreSQL), QMYSQL3 (MySQL), and
QDB2 (IBM DB2). В некоммерческие версии Qt входит только часть этого
набора. [1]Затем указывается сетевое имя сервера баз данных, имя базы данных, имя пользователя и пароль, после чего выполняется попытка установить соединение. Если функция open() завершилась неудачей -- выводится сообщение об ошибке, с помощью QSqlError::showMessage().
Обычно функция, подобная createConnection() вызывается из функции main():
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
if (!createConnection())
return 1;
...
return app.exec();
}
После установления соединения, посредством
QSqlQuery, можно выполнять SQL-запросы к базе данных.
Например, следующий код выполняет SQL-предложение -- SELECT:
QSqlQuery query;
query.exec("SELECT title, year FROM cd WHERE year >= 1998");
После вызова функции exec(), можно
просматривать полученный набор данных:
while (query.next()) {
QString title = query.value(0).toString();
int year = query.value(1).toInt();
cerr << title.ascii() << ": " << year << endl;
}
Первый вызов next() позиционирует
QSqlQuery на первую запись в наборе
данных. Последующие вызовы next()
передвигают указатель на следующую запись и так до тех пор, пока не
будет достигнут конец набора. В этой точке
next() вернет false.Функция value() возвращает значение поля в виде QVariant. Поля нумеруются, начиная с 0, в порядке их следования в предложении SELECT. Класс QVariant может хранить огромное количество типов языка C++ и Qt, в том числе int и QString. Различные типы данных, которые могут храниться в базе данных переводятся в соответствующие типы C++ и Qt, и сохраняются в виде QVariant. Например, VARCHAR представляется в виде QString, а DATETIME -- как QDateTime.
Класс QSqlQuery предоставляет целый набор функций для навигации по набору данных: first(), last(), prev(), seek() и at(). Они очень удобны в использовании, но на некоторых базах данных могут оказаться довольно медлительными и ресурсоемкими. С целью оптимизациии, при работе с большими наборами данных, можно вызвать QSqlQuery::setForwardOnly(true), перед exec(), а затем выполнять просмотр набора данных с помощью next(), правда в этом случае мы получаем, так называемые, однонаправленные наборы данных, т.е. такие наборы, навигация по которым может осуществляться только вперед, с помощью next().
Чуть выше говорилось о том, что SQL-запрос передается как аргумент функции exec(), но текст запроса может передаваться напрямую, конструктору QSqlQuery:
QSqlQuery query("SELECT title, year FROM cd WHERE year >= 1998");
Проверка на наличие ошибок и выдача сообщения могут быть
выполнены таким образом:
if (!query.isActive())
query.lastError().showMessage();
Выполнение предложения INSERT ничуть не сложнее, чем SELECT:
QSqlQuery query("INSERT INTO cd (id, artistid, title, year) "
"VALUES (203, 102, 'Living in America', 2002)");
После выполнения такого запроса, QSqlQuery::numRowsAffected() возвращает количество
записей, подвергшихся изменению (или -1, если база данных не
предусматривает поставку такой информации).В случае необходимости вставить в запрос значения переменных или когда нежелательно, или невозможно перевести аргументы предложения INSERT в строковый вид, можно построить параметризованный запрос, с помощью функции prepare(). Текст параметризованного запроса, вместо реальных значений содержит параметры, которые заполняются фактическими значениями после создания запроса. Qt поддерживает Oracle-подобный и ODBC-подобный стили именования параметров для всех типов баз данных. В примере ниже показано использование Oracle-подобного стиля именования:
QSqlQuery query(db);
query.prepare("INSERT INTO cd (id, artistid, title, year) "
"VALUES (:id, :artistid, :title, :year)");
query.bindValue(":id", 203);
query.bindValue(":artistid", 102);
query.bindValue(":title", QString("Living in America"));
query.bindValue(":year", 2002);
query.exec();
Теперь тот же самый пример, но в стиле ODBC:
QSqlQuery query(db);
query.prepare("INSERT INTO cd (id, artistid, title, year) "
"VALUES (?, ?, ?, ?)");
query.addBindValue(203);
query.addBindValue(102);
query.addBindValue(QString("Living in America"));
query.addBindValue(2002);
query.exec();
После создания запроса, вызовом prepare(), параметры запроса заполняются фактическими
значениями, с помощью функции bindValue()
или addBindValue(), после чего запрос
исполняется вызовом exec().
Параметризованные запросы можно выполнять в цикле. Перед началом цикла
создается запрос, а в теле цикла производится заполнение параметров
новыми значениями и исполнение запроса.Параметризованные запросы очень часто используются в тех случаях, когда в базу данных нужно записать двоичные данные или строки, которые содержат символы из наборов, не принадлежащих диапазону ASCII или Latin-1. Для баз данных, которые поддерживают Unicode, Qt использует эту кодировку символов, в других случаях выполняется преобразование строк в соответствующую кодировку.
Qt поддерживает механизм транзакций для баз данных, в которых он присутствует. Для запуска транзакции вызывается метод объекта QSqlDatabase -- transaction(). Для завершения транзакции вызывается либо функция commit(), либо rollback(). Например, выполним поиск по внешнему ключу и вставим запись в таблицу в рамках транзакции:
QSqlDatabase::database()->transaction();
QSqlQuery query;
query.exec("SELECT id FROM artist WHERE name = 'Gluecifer'");
if (query.next()) {
int artistId = query.value(0).toInt();
query.exec("INSERT INTO cd (id, artistid, title, year) "
"VALUES (201, " + QString::number(artistId)
+ ", 'Riding the Tiger', 1997)");
}
QSqlDatabase::database()->commit();
Функция QSqlDatabase::database()
возвращает указатель на объект QSqlDatabase,
который был создан в createConnection().
Если транзакция не может быть запущена, QSqlDatabase::transaction() возвращает
false.Некоторые базы данных не поддерживают механизм транзакций. В этом случае, функции transaction(), commit() и rollback() не выполняют никаких действий. Наличие поддержки механизма транзакций, той или иной базой данных, можно проверить с помощью метода hasFeature(), объекта QSqlDriver, ассоциированного с базой данных:
QSqlDriver *driver = QSqlDatabase::database()->driver();
if (driver->hasFeature(QSqlDriver::Transactions))
...
В примерах выше рассматривались случаи с единственным
подключением к базе данных. Однако ничто не мешает нам создать и
второе, и третье и т.д. соединения. В этом случае необходимо просто
передать имя соединения, вторым аргументом в функцию addDatabase():
QSqlDatabase *db = QSqlDatabase::addDatabase("QPSQL7", "OTHER");
db->setHostName("saturn.mcmanamy.edu");
db->setDatabaseName("starsdb");
db->setUserName("gilbert");
db->setPassword("ixtapa6");
Чтобы потом получить указатель на объект
QSqlDatabase, достаточно просто передать имя соединения в
функцию QSqlDatabase::database():
QSqlDatabase *db = QSqlDatabase::database("OTHER");
Для исполнения запросов через эти соединения, необходимо передать
объект QSqlDatabase конструктору
QSqlQuery:
QSqlQuery query(db);
query.exec("SELECT id FROM artist WHERE name = 'Mando Diao'");
Каждое соединение с базой данных может поддерживать только одну
активную транзакцию, поэтому множественные подключения могут оказаться
полезными в том случае, когда необходимо одновременно запустить
несколько транзакций. При использовании нескольких соединений, в
приложении по прежнему имеется одно неименованное соединение, которое
используется по-умолчанию объектами QSqlQuery, если им явно не указать с каким соединением
они должны работать.В дополнение к QSqlQuery, Qt предоставляет класс QSqlCursor, производный от QSqlQuery. Этот класс расширяет функциональность предка большим числом дополнительных методов, которые позволяют отказаться от написания SQL-запросов для наиболее употребимых SQL-операций, таких как: SELECT, INSERT, UPDATE и DELETE. Кроме того QSqlCursor выступает в роли посредника между QDataTable и базой данных. Далее, в этом разделе мы будем говорить о QSqlCursor, а в следующем разделе покажем, как можно использовать QDataTable, для представления наборов данных в табличной форме.
Следующий пример демонстрирует выполнение SQL-запроса -- SELECT:
QSqlCursor cursor("cd");
cursor.select("year >= 1998");
Эквивалентный вариант с использованием
QSqlQuery:
QSqlQuery query("SELECT id, artistid, title, year FROM cd "
"WHERE year >= 1998");
Навигация по набору данных выполняется точно так же, как и в
QSqlQuery, за одним маленьким исключением --
теперь, вместо порядкового номера поля, функции
value() можно передать его имя:
while (cursor.next()) {
QString title = cursor.value("title").toString();
int year = cursor.value("year").toInt();
cerr << title.ascii() << ": " << year << endl;
}
Для вставки записи в таблицу, предварительно нужно создать новую
запись QSqlRecord, вызовом primeInsert(), а затем, для каждого из полей, вызвать
setValue(). После всего этого можно
выполнить вставку функцией insert():
QSqlCursor cursor("cd");
QSqlRecord *buffer = cursor.primeInsert();
buffer->setValue("id", 113);
buffer->setValue("artistid", 224);
buffer->setValue("title", "Shanghai My Heart");
buffer->setValue("year", 2003);
cursor.insert();
Чтобы изменить запись -- нужно позиционировать QSqlCursor на запись, которая должна подвергнуться
изменениям (например, с помощью select() и
next()). Получить указатель на
QSqlRecord, вызовом primeUpdate(). После этого записать новые
значения функцией setValue() и
вызвать update(), чтобы отправить
сделанные изменения в базу данных:
QSqlCursor cursor("cd");
cursor.select("id = 125");
if (cursor.next()) {
QSqlRecord *buffer = cursor.primeUpdate();
buffer->setValue("title", "Melody A.M.");
buffer->setValue("year", buffer->value("year").toInt() + 1);
cursor.update();
}
Процедура удаления записи похожа на процедуру изменения:
QSqlCursor cursor("cd");
cursor.select("id = 128");
if (cursor.next()) {
cursor.primeDelete();
cursor.del();
}
Классы QSqlQuery и QSqlCursor реализуют интерфейс между Qt и базами
данных SQL. В следующих двух разделах мы покажем как они могут
использоваться в приложениях с графическим интерфейсом, которые
позволяют пользователю просматривать и изменять наборы данных,
хранящиеся в базе.
[1] |
От переводчика: кроме вышеперечисленных, Qt 3.2 включает в себя еще один драйвер, который уважаемые авторы, видимо по забывчивости, не указали -- это QIBASE (Interbase/Firebird). |
Пред. | В начало | След. |
Классы QString и QVariant. | На уровень выше | Представление данных в табличной форме. |