З’єднання з базою даних та виконання запитів - QT 4 програмування GUI на С

З'єднання з базою даних та виконання запитів

Для виконання запитів SQL необхідно спочатку встановити з'єднання з базою даних. Зазвичай налаштування з'єднань з базою даних виконується окремою функцією, яку ми викликаємо під час запуску програми. Наприклад:

01 bool createConnection()

03 QSqlDatabase *db = QSqlDatabase::addDatabase("QOCI8");

10 return false;

По-перше, ми викликаємо функцію QSqlDatabase::addDatabase() для створення об'єкта QSqlDatabase. Перший аргумент функції addDatabase() визначає драйвер бази даних, який Qt повинна використовувати для доступу до бази даних. В даному випадку ми використовуємо MySQL (. - У коді QOCI, Oracle Call Interface).

Потім ми встановлюємо ім'я хоста бази даних, ім'я бази даних, ім'я користувача та пароль, і ми відкриваємо з'єднання. Якщо функція open() завершується невдачею, ми виводимо повідомлення про помилку, використовуючи QSqlError::showMessage().

Зазвичай функцію createConnection() викликають у main():

01 int main(int argc, char *argv[])

03 QApplication app(argc, argv);

04 if (!createConnection())

06 return app.exec();

Після встановлення з'єднання ми можемо використовувати QSqlQuery для виконання будь-якої інструкції SQL, що підтримується базою даних. Нижче наведено приклад виконання команди SELECT:

query.exec("SELECT title, year FROM cd WHERE year >= 1998");

QString title = query.value(0).toString();

int year = query.value(1).toInt();

Ми викликаємо функцію next() один раз для позиціонування QSqlQuery на перший запис отриманого набору. Наступні дзвінки next() просувають покажчик запису на одну позицію далі, поки не буде досягнуто кінця, коли функція next() повертає false. Якщо результуючий набір(result set) порожній (або запит завершується невдачею) перший виклик функції next() поверне false.

Функція value() повертає значення поля, як QVariant. Поля пронумеровані починаючи з 0 у порядку їх вказівки у команді SELECT. Клас QVariant може містити багато типів C++ і Qt, включаючи int і QString. Інші типи даних, які можуть зберігатися в базі даних, перетворюються на відповідні типи С++ і Qt і зберігаються QVariant. Наприклад, VARCHAR представляється як QString, a DATETIME — як QDateTime.

Раніше ми запитували SQL в аргументі функції QSqlQuery::exec(), але, крім того, ми можемо передавати його безпосередньо конструктору, який відразу ж виконає його:

QSqlQuery query("SELECT title, year FROM cd WHERE year >= 1998");

Ми можемо перевірити наявність помилки, викликаючи функцію isActive() для запиту:

QMessageBox::warning(this, tr("Database Error"),

Якщо помилки немає, запит стає активним і ми можемо використовувати next() для переміщення по результуючого набору.

Виконання команди INSERT здійснюється майже так само просто, як і команди SELECT:

QSqlQuery query("INSERT INTO cd (id, artistid, title, year)"

"VALUES (203, 102, 'Living in America', 2002)");

Після цього функція numRowsAffected() повертає кількість рядків, змінених інструкцією SQL (або -1, якщо виникла помилка).

Якщо нам необхідно вставляти багато записів або якщо ми хочемо уникнути перетворення значень на рядкові дані (і правильного перетворення спеціальних символів), ми можемо використовувати функцію prepare() для вказівки полів у шаблоні запиту і потім надання їм необхідних нам значень. Qt підтримує як стиль Oracle, так і стиль ODBC для всіх баз даних, застосовуючи деможливо, «рідний» інтерфейс бази даних або імітуючи його інакше. Нижче наведено приклад, у якому використовується синтаксис Oracle для представлення іменованих полів:

query.prepare("INSERT INTO cd (id, artistid, title, year)"

"VALUES (:id, :artistid, :title, :year)");

query.bindValue(":title", "Living in America");

Нижче наведено той самий приклад позиційного представлення полів у стилі ODBC:

query.prepare("INSERT INTO cd (id, artistid, title, year)"

query.addBindValue("Living in America");

Після виклику функції exec() ми можемо викликати bindValue() або addBindValue() для надання нових значень, потім знову викликати exec() для виконання запиту вже з новими значеннями.

Такі шаблони часто використовуються для завдання двійкових рядкових даних, що містять символи не в ASCII або Latin-1. Непомітно для користувача Qt використовує Unicode у тих базах даних, які підтримують Unicode, а тих, які не роблять цього, Qt також непомітно для користувача перетворює рядкові дані у відповідне кодування.

Qt підтримує SQL-транзакції в тих базах даних, де вони передбачені. Для запуску транзакції ми викликаємо функцію transaction() для об'єкта QSqlDatabase, що представляє з'єднання з базою даних. Для завершення транзакції ми або функцію commit(), або функцію rollback(). Наприклад, нижче показано, як ми можемо знайти зовнішній ключ (foreign key) та виконати команду INSERT усередині транзакції:

query.exec("INSERT INTO cd (id, artistid, title, year)"

"VALUES (201, " + QString::number(artistId)

+ ", 'Riding the Tiger', 1997)");

Функція QSqlDatabase::database() повертає об'єкт QSqlDatabase, що представляє з'єднання, створене нами під час викликуcreateConnection(). Якщо транзакція не може запуститись, функція QSqlDatabase::transaction() повертає false. Деякі бази даних не підтримують транзакції. У цьому випадку функції transaction(), commit() та rollback() нічого не роблять. Ми можемо перевірити можливість підтримки базою даних транзакцій шляхом виклику функції hasFeature() для об'єкта QSqlDriver, пов'язаного з базою даних:

QSqlDriver *driver = QSqlDatabase::database().driver();

Можна перевірити наявність у базі даних інших можливостей, включаючи підтримку об'єктів BLOB (Binary Large Objects — великі двійкові об'єкти), Unicode і підготовлених запитів.

У прикладах, що наводяться досі, ми припускали, що в додатку використовується одне з'єднання з базою даних. Якщо ми хочемо створити кілька з'єднань, ми можемо передавати назву з'єднання як другий аргумент функції addDatabase(). Наприклад:

QSqlDatabase *db = QSqlDatabase::addDatabase("QPSQL", "OTHER");

Потім можемо отримати покажчик на об'єкт QSqlDatabase, передаючи назву з'єднання функції QSqlDatabase::database():

QSqlDatabase db = QSqlDatabase::database("OTHER");

Для виконання запитів з іншим з'єднанням ми передаємо об'єкт QSqlDatabase конструктору QSqlQuery:

Декілька з'єднань корисні, якщо ми хочемо одночасно виконувати кілька транзакцій, оскільки кожне з'єднання може використовуватися тільки для однієї активної транзакції. Коли ми використовуємо кілька з'єднань з базою даних, ми можемо все-таки мати одне неназване з'єднання і QSqlQuery буде використовувати це з'єднання, якщо не вказано назву.

Крім QSqlQuery Qt містить клас QSqlTableModel - інтерфейс високого рівня, дозволяючи нам не використовувати вирази SQLчистому вигляді» для виконання найбільш поширених SQL-команд (SELECT, INSERT, UPDATE та DELETE). Цей клас може використовуватися автономно без будь-якого графічного інтерфейсу користувача або в якості джерела даних для QListView або QTableView.

Нижче наведено приклад використання QSqlTableModel для виконання команди SELECT:

Це еквівалентно запиту

SELECT * FROM cd WHERE year >= 1998

Перегляд результуючого набору здійснюється шляхом отримання заданого запису функцією QSqlTableModel::record() та доступу до окремих полів за допомогою функції value():

QSqlRecord record = model.record(i);

QString title = record.value("title").toString();

int year = record.value("year").toInt();

Функція QSqlRecord::value() приймає або ім'я поля або індекс поля. При роботі з великими наборами даних рекомендується задавати поля за допомогою їх індексів. Наприклад:

int titleIndex = model.record().indexOf("title");

int yearIndex = model.record().indexOf("year");

QSqlRecord record = model.record(i);

QString title = record.value(titleIndex).toString();

int year = record.value(yearIndex).toInt();

Для вставки запису в таблицю бази даних ми діємо так само, як коли б робили вставку в двовимірну модель: спочатку викликаємо функцію insertRow() для створення нового порожнього рядка (запису) і потім використовуємо setData() для встановлення значення кожного стовпця (поля запису) .

model.setData(model.index(row, 0), 113);

model.setData(model.index(row, 1), "Shanghai My Heart");

model.setData(model.index(row, 2), 224);

model.setData(model.index(row, 3), 2003);

Після виклику submitAll() запис може бути переміщений в іншу позицію, яка залежить відвпорядкованості таблиці. Виклик submitAll() поверне false, якщо вставка виявиться невдалою.

Важливою відмінністю моделі SQL від стандартної моделі є необхідність виклику в моделі SQL функції submitAll() для запису всіх змін до бази даних

Для оновлення запису ми повинні спочатку встановити QSqlTableModel на запис, який хочемо модифікувати (наприклад, використовуючи функції select()). Потім ми отримуємо запис, оновлюємо відповідні поля та записуємо наші зміни назад до бази даних:

if (model.rowCount() == 1)

QSqlRecord record = model.record(0);

record.setValue("title", "Melody A.M.");

record.setValue("year", record.value("year").toInt() + 1);

Якщо є запис, що відповідає заданому фільтру, доступ до нього ми отримуємо за допомогою функції QSqlTableModel::record(). Ми здійснюємо наші зміни та знову записуємо до бази даних запис із новими значеннями полів.

Крім того, оновлення можна виконати за допомогою функції setData(), як це робиться для моделі, яка відрізняється від SQL-моделі. Для отримання доступу до полів запису використовуються індекси моделі із зазначенням номера рядка (запису) та стовпця (поля):

if (model.rowCount() == 1)

model.setData(model.index(0, 1), "Melody A.M.");

model.data(model.index(0, 3)).toInt() + 1);

Видалення запису нагадує його оновлення:

if (model.rowCount() == 1)

У виклику removeRows() вказуються номер рядка першого запису, що видаляється, і кількість записів, що видаляються. У наступному прикладі видаляються всі записи, що задовольняють фільтру:

if (model.rowCount() > 0)

Класи QSqlQuery та QSqlTableModel забезпечують інтерфейс між Qt та базою даних SQL. Використовуючи ці класи, можна створювати форми, що становлять данікористувачам та які дозволяють їм вставляти, оновлювати та видаляти записи.