Программирование на C++ с использованием библиотеки Qt4

         

Выполнение SQL-запросов (файл examples-qt/db00/db00.h)


1 #include <QtGui>
2 3 class MyDialog : public QDialog { 4 Q_OBJECT 5 public: 6 MyDialog(QWidget *parent=0);
7 protected: 8 virtual void closeEvent(QCloseEvent *event);
9 private slots: 10 bool start();
11 private: 12 QComboBox *mode; // Режим (драйвер). 13 QLineEdit *host; // Хост. 14 QLineEdit *dbname; // Имя БД. 15 QLineEdit *user; // Пользователь. 16 QLineEdit *password; // Пароль. 17 QTextEdit *scr; // Для вывода сообщений. 18 QPushButton *btnStart; // Кнопка 'Старт'. 19 ;


1 #include <QtGui>
2 #include <QtSql>
3 #include <QtTest/QtTest>
// Для qWait(). 4 5 #include "db00.h" 6 7 MyDialog::MyDialog(QWidget *parent) 8 : QDialog(parent) { 9 QTextCodec *codec = QTextCodec::codecForName("CP1251");
10 QTextCodec::setCodecForTr(codec);
11 QTextCodec::setCodecForCStrings(codec);
12 QTextCodec::setCodecForLocale(codec);
13 14 setWindowFlags(Qt::Window);
15 16 mode = new QComboBox(this);
17 QStringList drivers = QSqlDatabase::drivers();
18 drivers.removeAll("QMYSQL3");
19 drivers.removeAll("QOCI8");
20 drivers.removeAll("QODBC3");
21 drivers.removeAll("QPSQL7");
22 drivers.removeAll("QTDS7");
23 mode->
addItems(drivers);
24 25 host = new QLineEdit(tr("localhost"), this);
26 dbname = new QLineEdit(this);
27 user = new QLineEdit(this);
28 password = new QLineEdit(this);
29 password->
setEchoMode(QLineEdit::Password);
30 31 btnStart = new QPushButton(tr("Старт"), this);
32 33 scr = new QTextEdit(this);
34 scr->
setReadOnly(true);
35 36 QGridLayout *layout = new QGridLayout(this);
37 layout->
addWidget(new QLabel(tr("Режим:"), this), 38 0, 0, Qt::AlignRight);
39 layout->
addWidget(mode, 0, 1, 1, 3);
40 41 layout->
addWidget(new QLabel(tr("Хост:"), this), 42 1, 0, Qt::AlignRight);
43 layout->
addWidget(host, 1, 1);
44 45 layout->
addWidget(new QLabel(tr("База данных:"), this), 46 1, 2, Qt::AlignRight);
47 layout->
addWidget(dbname, 1, 3);
48 49 layout->
addWidget(new QLabel(tr("Пользователь:"), this), 50 2, 0, Qt::AlignRight);
51 layout->
addWidget(user, 2, 1);
52 53 layout->
addWidget(new QLabel(tr("Пароль:"), this), 54 2, 2, Qt::AlignRight);
55 layout->
addWidget(password, 2, 3);
56 57 layout->
addWidget(btnStart, 3, 1, 1, 2);
58 layout->
addWidget(scr, 4, 0, 1, 4);
59 60 layout->
setMargin(6);
61 layout->
setSpacing(5);
62 layout->
setColumnStretch(1, 1);
63 layout->
setColumnStretch(3, 1);
64 layout->
setRowStretch(4, 1);
65 setLayout(layout);
66 67 connect(btnStart, SIGNAL(clicked()), this, SLOT(start()));
68 } 69 70 bool MyDialog::start() { 71 scr->
append(tr("Соединяюсь с базой данных..."));
72 QSqlDatabase db = QSqlDatabase::addDatabase( 73 mode->
currentText() );
74 db.setHostName(host->
text());
75 db.setDatabaseName(dbname->
text());
76 db.setUserName(user->
text());
77 db.setPassword(password->
text());
78 if (db.open()) { 79 mode->
setEnabled(false);
80 host->
setEnabled(false);
81 dbname->
setEnabled(false);
82 user->
setEnabled(false);
83 password->
setEnabled(false);
84 btnStart->
setEnabled(false);
85 scr->
append(tr("Соединение установлено!"));
86 }else{ 87 scr->
append(tr("Не могу соединиться: "));
88 scr->
append(db.lastError().text());
89 return false; 90 } 91 92 QSqlQuery sql = QSqlQuery();
93 //sql.exec(tr("SET NAMES 'cp1251'"));
94 QStringList dbtables = db.tables(QSql::Tables);
95 if (dbtables.contains( tr("employee"), 96 Qt::CaseInsensitive)) { 97 scr->
append( tr( 98 "Таблица \"employee\" уже существует."));
99 sql.exec(tr("DROP TABLE employee"));
100 if ( sql.lastError().type() == QSqlError::NoError ) { 101 scr->
append( tr( 102 "Удалили таблицу \"employee\" "));
103 }else{ 104 scr->
append( tr( 105 "Не могу удалить таблицу \"employee\":"));
106 scr->
append(sql.lastError().text());
107 return false; 108 } 109 } 110 111 sql.exec( tr( 112 "create table employee ( " 113 " id integer PRIMARY KEY, " 114 " name char(30) not null, " 115 " born date null, " 116 " salary numeric(12,2), " 117 " married boolean NULL ) " ) );
118 if ( sql.lastError().type() == QSqlError::NoError ) { 119 scr->
append( tr( 120 "Создали таблицу \"employee\"."));
121 }else{ 122 scr->
append( tr( 123 "Не могу создать таблицу \"employee\":"));
124 scr->
append(sql.lastError().text());
125 return false; 126 } 127 128 if (sql.prepare( tr( 129 "INSERT INTO employee " 130 " VALUES (?, ?, ?, ?, ?)") ) ) { 131 int arr_id[] = {123, 345, 501}; 132 QString arr_name[] = {tr("Винни-Пух"), 133 tr("Ослик Иа"), 134 tr("Поросёнок")}; 135 QDate arr_born[] = {QDate(1971, 12, 31), 136 QDate(1965, 2, 23), 137 QDate(1982, 6, 14)}; 138 float arr_salary[] = {1234.56f, 2345.67f, 871}; 139 int arr_married[] = {1, 0, 0}; 140 141 for (unsigned int i=0; i < 3; i++) { 142 sql.bindValue(0, arr_id[i]);
143 sql.bindValue(1, arr_name[i]);
144 sql.bindValue(2, arr_born[i]);
145 sql.bindValue(3, arr_salary[i]);
146 sql.bindValue(4, arr_married[i]);
147 sql.exec();
148 if ( sql.lastError().type() == QSqlError::NoError ) { 149 scr->
append( tr( 150 "Вставили новую запись."));
151 }else{ 152 scr->
append( tr( 153 "Не могу вставить новую запись:"));
154 scr->
append(sql.lastError().text());
155 return false; 156 } 157 } 158 }else{ 159 scr->
append( tr( 160 "Не могу подготовить запрос:"));
161 scr->
append(sql.lastError().text());
162 return false; 163 } 164 165 sql.exec( tr("SELECT * FROM employee ") );
166 if ( sql.isActive() ) { 167 QSqlRecord rec = sql.record();
168 scr->
append( tr( 169 "В таблице \"employee\" %1 столбцов: ") 170 .arg(rec.count() ) );
171 172 QString fields; 173 for(int j=0; j<rec.count();
j++) 174 fields += rec.fieldName(j) + ", "; 175 176 scr->
append(fields);
177 178 scr->
append( tr( 179 "В таблице \"employee\" %1 записей: ") 180 .arg(sql.size() ) );
181 182 while ( sql.next() ) { 183 int id = sql.value(0).toInt();
184 QString name = sql.value(1).toString();
185 QDate born = sql.value(2).toDate();
186 double salary = sql.value(3).toDouble();
187 bool married = sql.value(4).toBool();
188 scr->
append( tr( 189 "%1\t %2\t %3\t %4\t %5") 190 .arg(id) 191 .arg(name) 192 .arg(born.toString(tr("dd/MM/yyyy"))) 193 .arg(salary) 194 .arg(married) );
195 } 196 }else{ 197 scr->
append( tr( 198 "Не могу получить данные:"));
199 scr->
append(sql.lastError().text());
200 return false; 201 } 202 203 scr->
append( tr( 204 "При закрытии окна соединение с БД будет завершено."));
205 return true; 206 } 207 208 void MyDialog::closeEvent(QCloseEvent *event) { 209 QSqlDatabase db = QSqlDatabase::database();
210 if (db.isOpen()) { 211 db.close();
212 scr->
append("--------------------------");
213 scr->
append(tr("Соединение с базой данных закрыто!"));
214 QTest::qWait(1000);
// Ждать 1 сек. 215 } 216 } 217 218 int main(int argc, char *argv[]) { 219 QApplication app(argc, argv);
220 221 MyDialog *mainWin = new MyDialog();
222 mainWin->
show();
223 return app.exec();
224 }




(13) Установили для диалога флаг Qt::Windows, чтобы в заголовке окна появились кнопки сворачивания и восстановления. (15-22) Создали поле со списком и заполнили его названиями всех доступных драйверов SQL, кроме устаревших (17-21). (24-28) Создали однострочные поля для ввода параметров соединения. (30) Кнопка Старт. (32-33) Многострочное поле для вывода сообщений. (35-64) Расположили все элементы с помощью сеточного менеджера размещения.

(66) Связали кнопку Старт с функцией start(). (69) Функция, выполняемая при нажатии на кнопку. (70) Вывели сообщение. (72-77) Создали соединение с базой данных. (78-83) Если соединение установлено, то менять параметры уже нельзя. (93) При необходимости сообщили серверу кодовую таблицу клиента. (94-95) Выяснили, существует ли в базе данных таблица employee (работники). (98) Если да, то удалили её. (110) Создали новую таблицу. (127-162) Выполнили серию SQL-запросов с параметрами для вставки новых записей в созданную таблицу. (164) Получили все записи таблицы. (166-173) Получили информацию о полях (столбцах). (177-193) Вывели на экран все записи (строки) таблицы. (207) Виртуальная функция closeEvent выполняется при закрытии окна. (209-210) Если соединение с базой данных открыто, то закрываем его. (211-213) Выводим сообщение и ждём секунду перед закрытием окна. Для компиляции проектов, использующих модуль QtTest, в файл проекта *.pro надо добавить строку CONFIG += qtestlib.

Перед компиляцией проекта, в котором используется модуль QtSql, надо добавить в pro-файл строку QT += sql.

Перед выполнением этой программы требуется создать какую-нибудь базу данных: create database db1 character set utf8;

(вместо utf8 можно использовать кодировки cp1251 или koi8r);
а перед использованием QODBC -- настроить ODBC-псевдоним.


Подключение с базе данных и выполнение SQL-запросов


Для подключения к базе данных надо указать название SQL-драйвера, например: QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "MyDB1"); Второй необязательный параметр позволяет задать имя соединения.

Затем указывается имя сервера, название базы данных, имя пользователя и пароль: db.setHostName("localhost"); // или, например, "my1.server.ru" db.setDatabaseName("mydb1"); db.setUserName("root"); db.setPassword("mypassword"); Если сервер использует нестандартный порт, то придётся задать и его: db.setPort(НомерПорта); В случае использования QODBC имя сервера не требуется, а вместо названия базы данных указывается ODBC-псевдоним (алиас).

SQLite не поддерживает авторизацию пользователей, поэтому ему требуется указать только имя файла данных. Предопределённое имя ":memory:" позволяет размещать временную базу данных в оперативной памяти.

После того, как все параметры подключения заданы, можно открыть соединение: bool connected = db.open(); Если подключение установить не удалось, то не плохо бы узнать описание ошибки и сообщить его пользователю: if (!connected) { QMessageBox::critical( // Диалог с сообщением об ошибке. parent, // Родительский виджет. QObject::tr("Database Error"), // Заголовок. db.lastError().text()); // Текст сообщения. return false; // Вернуть признак неудачного подключения. } Если подключение установлено, то можно выполнить любой SQL-запрос, например: QSqlQuery sql; sql.exec("SELECT id, name, salary FROM empl WHERE salary>=1000"); или QSqlQuery sql("SELECT id, name, salary FROM empl WHERE salary>=1000"); Здесь запрашиваются номера id, имена name и оклады salary всех работников из таблицы empl, у которых оклад не ниже 1000. Обратите внимание, что если при создании объекта QSqlQuery указан текст запроса на языке SQL, то этот запрос сразу выполняется.

В обоих случаях в конструкторе запроса можно указать базу данных QSqlDatabase, с которой он будет работать. Но как правило, приложение открывает только одно соединение с единственной базой данных, поэтому этот параметр принимает значение по умолчанию.

Если при выполнении запроса возникла ошибка, то метод lastError() позволяет вывести на экран её описание: if ( ! query.isActive() ) QMessageBox::warning( this, tr("Database Error"), query.lastError().text() ); Иначе можно получить данные, которые сервер вернул в качестве результата: while ( sql.next() ) { qint64 id = sql.value(0).toLongLong(); QString name = sql.value(1).toString(); double salary = sql.value(2).toDouble(); // ....... } Метод QSqlQuery::next() переводит курсор на очередную запись результирующего набора данных или возвращает false, если достигнут его конец. Метод value(номер_столбца) возвращает значение типа QVariant, которое надо преобразовать к нужному типу с помощью методов QVariant::toInt, QVariant::toLongLong, QVariant::toString, QVariant::toDouble, QVariant::toDate, QVariant::toDateTime и т.д.

Кроме next(), для навигации по набору данных можно использовать методы first(), last(), previous(), seek(int index, bool relative=false). Для увеличения быстродействия набор данных лучше сделать однонаправленным, вызвав метод QSqlQuery::setForwardOnly(true) до выполнения запроса, после этого можно использовать только next().

Метод QSqlQuery::size() возвращает число записей, полученных в результате выполнения запроса SELECT (-1, если была ошибка или если драйвер данной СУБД не поддерживает эту функцию). При выполнении SQL-запросов INSERT, UPDATE или DELETE вместо size() надо использовать метод QSqlQuery::numRowsAffected(). Чтобы узнать, возникла ли ошибка при последнем выполнении запроса, используется метод QSqlQuery::lastError(). Аналогичный метод имеет и класс QSqlDatabase. В обоих случаях возвращается экземпляр класса QSqlError. Тип ошибки можно выяснить, вызвав метод QSqlError::type(). Возможные типы ошибок: QSqlError::NoError (ошибок не было), QSqlError::ConnectionError (ошибка соединения), QSqlError::StatementError (синтаксическая ошибка в SQL-запросе), QSqlError::TransactionError (ошибка транзакции) и QSqlError::UnknownError (неизвестная ошибка).

Если требуется выполнить большое количество однотипных SQL-операторов, то эффективнее использовать запрос с параметрами: QSqlQuery query; sql.prepare("INSERT INTO empl (id, name, salary) " "VALUES (:id, :name, :salary)"); for (int i=0; i<N; i++) { sql.bindValue(":id", arr_id[i]); sql.bindValue(":name", arr_name[i]); sql.bindValue(":salary", arr_salary[i]); sql.exec(); } или, что же самое (только параметры безымянные): QSqlQuery query; sql.prepare("INSERT INTO empl (id, name, salary) " "VALUES (?, ?, ?)"); for (int i=0; i<N; i++) { sql.addBindValue(arr_id[i]); sql.addBindValue(arr_name[i]); sql.addBindValue(arr_salary[i]); sql.exec(); } Здесь выполняется вставка N записей, данные берутся из массивов arr_id, arr_name и arr_salary.

Если СУБД поддерживает механизм транзакций, то для начала новой транзакции используется метод bool QSqlDatabase::transaction() для её подтверждения надо вызвать bool QSqlDatabase::commit() а для отмены: bool QSqlDatabase::rollback() Если СУБД не поддерживает транзакций, то вызовы transaction, commit и rollback ничего не делают. С помощью метода QSqlDriver::hasFeature() можно узнать, поддерживается ли данным драйвером и СУБД та или иная функция, в том числе и транзакции: QSqlDriver *driver = QSqlDatabase::database().driver(); if (driver->hasFeature(QSqlDriver::Transactions)) ....... Каждое соединение с базой данных может иметь только одну активную транзакцию. Если этого недостаточно, всегда можно открыть ещё несколько соединений с той же базой данных.

Ссылку на соединение с базой данных можно получить, вызвав функцию QSqlDatabase::database(connectionName). Необязательный параметр connectionName -- это имя соединения, которое было задано при его создании с помощью QSqlDatabase::addDatabase().

По окончании работы с базой данных соединение надо закрыть: QSqlDatabase::close(). Затем можно либо открыть его снова с помощью метода open(), либо удалить из списка соединений, вызвав статический метод QSqlDatabase::removeDatabase(connectionName).


Выполнение SQL-запросов (система Windows, драйвер QODBC)


Выполнение SQL-запросов (система Linux, драйвер QMYSQL)

В листингах приведён пример программы, работающей с базой данных, а на рис. показан результат её работы в Windows и Linux.