The OpenNET Project / Index page

[ новости /+++ | форум | wiki | теги | ]

Каталог документации / Раздел "Программирование, языки" / Оглавление документа

Глава 12. Базы данных.

Модуль SQL, в библиотеке Qt, предоставляет независимый от типа платформы и базы данных интерфейс, для доступа к базам данных SQL, и набор классов, обеспечивающих взаимодействие пользовательского интерфейса с базами данных.

Глава начинается с демонстрационного примера, который показывает, как установить соединение с базой данных и как выполнить произвольный SQL-код. Во втором и третьем разделах мы подробнее остановимся на том, как предоставить пользователю возможность просматривать и изменять наборы данных, используя QDataTable -- для просмотра данных в табличном виде, и QSqlForm -- в виде формы.

12.1. Установление соединения и выполнение запроса.

Прежде чем выполнить запрос к базе данных, для начала необходимо установить с ней соединение. Как правило, установление соединения с базой данных выполняется в виде отдельной функции, которую приложение вызывает на запуске, например:

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).




Спонсоры:
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2021 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру