Qt 提供了 QtSql 模塊來提供平臺獨立的基于 SQL 的數(shù)據(jù)庫操作,。這里我們所說的“平臺獨立”,,既包括操作系統(tǒng)平臺,有包括各個數(shù)據(jù)庫平臺,。另外,,我們強調(diào)了“基于 SQL”,因為 NoSQL 數(shù)據(jù)庫至今沒有一個通用查詢方法,,所以不可能提供一種通用的 NoSQL 數(shù)據(jù)庫的操作,。Qt 的數(shù)據(jù)庫操作還可以很方便的與 model/view 架構(gòu)進行整合。通常來說,我們對數(shù)據(jù)庫的操作更多地在于對數(shù)據(jù)庫表的操作,,而這正是 model/view 架構(gòu)的長項,。
Qt 使用QSqlDatabase 表示一個數(shù)據(jù)庫連接。更底層上,,Qt 使用驅(qū)動(drivers)來與不同的數(shù)據(jù)庫 API 進行交互,。Qt 桌面版本提供了如下幾種驅(qū)動:
驅(qū)動 |
數(shù)據(jù)庫 |
QDB2 |
IBM DB2 (7.1 或更新版本) |
QIBASE |
Borland InterBase |
QMYSQL |
MySQL |
QOCI |
Oracle Call Interface Driver |
QODBC |
Open Database Connectivity (ODBC) – Microsoft SQL Server 及其它兼容 ODBC 的數(shù)據(jù)庫 |
QPSQL |
PostgreSQL (7.3 或更新版本) |
QSQLITE2 |
SQLite 2 |
QSQLITE |
SQLite 3 |
QSYMSQL |
針對 Symbian 平臺的SQLite 3 |
QTDS |
Sybase Adaptive Server (自 Qt 4.7 起廢除) |
不過,由于受到協(xié)議的限制,,Qt 開源版本并沒有提供上面所有驅(qū)動的二進制版本,,而僅僅以源代碼的形式提供。通常,,Qt 只默認搭載 QSqlite 驅(qū)動(這個驅(qū)動實際還包括 Sqlite 數(shù)據(jù)庫,,也就是說,如果需要使用 Sqlite 的話,,只需要該驅(qū)動即可),。我們可以選擇把這些驅(qū)動作為 Qt 的一部分進行編譯,也可以當(dāng)作插件編譯,。
如果習(xí)慣于使用 SQL 語句,,我們可以選擇QSqlQuery 類;如果只需要使用高層次的數(shù)據(jù)庫接口(不關(guān)心 SQL 語法),,我們可以選擇QSqlTableModel 和QSqlRelationalTableModel ,。本章我們介紹QSqlQuery 類,在后面的章節(jié)則介紹QSqlTableModel 和QSqlRelationalTableModel ,。
在使用時,,我們可以通過
找到系統(tǒng)中所有可用的數(shù)據(jù)庫驅(qū)動的名字列表。我們只能使用出現(xiàn)在列表中的驅(qū)動,。由于默認情況下,,QtSql 是作為 Qt 的一個模塊提供的。為了使用有關(guān)數(shù)據(jù)庫的類,,我們必須早 .pro 文件中添加這么一句:
這表示,,我們的程序需要使用 Qt 的 core、gui 以及 sql 三個模塊,。注意,,如果需要同時使用 Qt4 和 Qt5 編譯程序,通常我們的 .pro 文件是這樣的:
|
QT += core gui sql greaterThan(QT_MAJOR_VERSION, 4): QT += widgets |
這兩句也很明確:Qt 需要加載 core,、gui 和 sql 三個模塊,,如果主板本大于 4,則再添加 widgets 模塊,。
下面來看一個簡單的函數(shù):
|
bool connect(const QString &dbName) { QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); // db.setHostName("host"); // db.setDatabaseName("dbname"); // db.setUserName("username"); // db.setPassword("password"); db.setDatabaseName(dbName); if (!db.open()) { QMessageBox::critical(0, QObject::tr("Database Error"), db.lastError().text()); return false; } return true; } |
我們使用connect() 函數(shù)創(chuàng)建一個數(shù)據(jù)庫連接,。我們使用QSqlDatabase::addDatabase() 靜態(tài)函數(shù)完成這一請求,,也就是創(chuàng)建了一個QSqlDatabase 實例。注意,,數(shù)據(jù)庫連接使用自己的名字進行區(qū)分,,而不是數(shù)據(jù)庫的名字。例如,,我們可以使用下面的語句:
|
QSqlDatabase db=QSqlDatabase::addDatabase("QSQLITE", QString("con%1").arg(dbName)); |
此時,,我們是使用addDatabase() 函數(shù)的第二個參數(shù)來給這個數(shù)據(jù)庫連接一個名字。在這個例子中,,用于區(qū)分這個數(shù)據(jù)庫連接的名字是QString("conn%1").arg(dbName) ,,而不是 “QSQLITE”。這個參數(shù)是可選的,,如果不指定,系統(tǒng)會給出一個默認的名字QSqlDatabase::defaultConnection ,,此時,,Qt 會創(chuàng)建一個默認的連接。如果你給出的名字與已存在的名字相同,,新的連接會替換掉已有的連接,。通過這種設(shè)計,我們可以為一個數(shù)據(jù)庫建立多個連接,。
我們這里使用的是 sqlite 數(shù)據(jù)庫,,只需要指定數(shù)據(jù)庫名字即可。如果是數(shù)據(jù)庫服務(wù)器,,比如 MySQL,,我們還需要指定主機名、端口號,、用戶名和密碼,,這些語句使用注釋進行了簡單的說明。
接下來我們調(diào)用了QSqlDatabase::open() 函數(shù),,打開這個數(shù)據(jù)庫連接,。通過檢查open() 函數(shù)的返回值,我們可以判斷數(shù)據(jù)庫是不是正確打開,。
QtSql 模塊中的類大多具有lastError() 函數(shù),,用于檢查最新出現(xiàn)的錯誤。如果你發(fā)現(xiàn)數(shù)據(jù)庫操作有任何問題,,應(yīng)該使用這個函數(shù)進行錯誤的檢查,。這一點我們也在上面的代碼中進行了體現(xiàn)。當(dāng)然,,這只是最簡單的實現(xiàn),,一般來說,,更好的設(shè)計是,不要在數(shù)據(jù)庫操作中混雜界面代碼(并且將這個connect() 函數(shù)放在一個專門的數(shù)據(jù)庫操作類中),。
接下來我們可以在main() 函數(shù)中使用這個connect() 函數(shù):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
int main(int argc, char *argv[]) { QApplication a(argc, argv); if (connect("demo.db")) { QSqlQuery query; if (!query.exec("CREATE TABLE student (" "id INTEGER PRIMARY KEY AUTOINCREMENT," "name VARCHAR," "age INT)")) { QMessageBox::critical(0, QObject::tr("Database Error"), query.lastError().text()); return 1; } } else { return 1; } return a.exec(); } |
main() 函數(shù)中,,我們調(diào)用這個connect() 函數(shù)打開數(shù)據(jù)庫。如果打開成功,,我們通過一個QSqlQuery 實例執(zhí)行了 SQL 語句,。同樣,我們使用其lastError() 函數(shù)檢查了執(zhí)行結(jié)果是否正確,。
注意這里的QSqlQuery 實例的創(chuàng)建,。我們并沒有指定是為哪一個數(shù)據(jù)庫連接創(chuàng)建查詢對象,此時,,系統(tǒng)會使用默認的連接,,也就是使用沒有第二個參數(shù)的addDatabase() 函數(shù)創(chuàng)建的那個連接(其實就是名字為QSqlDatabase::defaultConnection 的默認連接)。如果沒有這么一個連接,,系統(tǒng)就會報錯,。也就是說,如果沒有默認連接,,我們在創(chuàng)建QSqlQuery 對象時必須指明是哪一個QSqlDatabase 對象,,也就是addDatabase() 的返回值。
我們還可以通過使用QSqlQuery::isActive() 函數(shù)檢查語句執(zhí)行正確與否,。如果QSqlQuery 對象是活動的,,該函數(shù)返回 true。所謂“活動”,,就是指該對象成功執(zhí)行了exec() 函數(shù),,但是還沒有完成。如果需要設(shè)置為不活動的,,可以使用finish() 或者clear() 函數(shù),,或者直接釋放掉這個QSqlQuery 對象。這里需要注意的是,,如果存在一個活動的 SELECT 語句,,某些數(shù)據(jù)庫系統(tǒng)不能成功完成connect() 或者rollback() 函數(shù)的調(diào)用。此時,,我們必須首先將活動的 SELECT 語句設(shè)置成不活動的,。
創(chuàng)建過數(shù)據(jù)庫表 student 之后,我們開始插入數(shù)據(jù),,然后將其獨取出來:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
if (connect("demo.db")) { QSqlQuery query; query.prepare("INSERT INTO student (name, age) VALUES (?, ?)"); QVariantList names; names << "Tom" << "Jack" << "Jane" << "Jerry"; query.addBindValue(names); QVariantList ages; ages << 20 << 23 << 22 << 25; query.addBindValue(ages); if (!query.execBatch()) { QMessageBox::critical(0, QObject::tr("Database Error"), query.lastError().text()); } query.finish(); query.exec("SELECT name, age FROM student"); while (query.next()) { QString name = query.value(0).toString(); int age = query.value(1).toInt(); qDebug() << name << ": " << age; } } else { return 1; } |
依舊連接到我們創(chuàng)建的 demo.db 數(shù)據(jù)庫,。我們需要插入多條數(shù)據(jù),此時可以使用QSqlQuery::exec() 函數(shù)一條一條插入數(shù)據(jù),,但是這里我們選擇了另外一種方法:批量執(zhí)行,。首先,,我們使用QSqlQuery::prepare() 函數(shù)對這條 SQL 語句進行預(yù)處理,問號 ? 相當(dāng)于占位符,,預(yù)示著以后我們可以使用實際數(shù)據(jù)替換這些位置,。簡單說明一下,預(yù)處理是數(shù)據(jù)庫提供的一種特性,,它會將 SQL 語句進行編譯,,性能和安全性都要優(yōu)于普通的 SQL 處理。在上面的代碼中,,我們使用一個字符串列表 names 替換掉第一個問號的位置,,一個整型列表 ages 替換掉第二個問號的位置,利用QSqlQuery::addBindValue() 我們將實際數(shù)據(jù)綁定到這個預(yù)處理的 SQL 語句上,。需要注意的是,,names 和 ages 這兩個列表里面的數(shù)據(jù)需要一一對應(yīng)。然后我們調(diào)用QSqlQuery::execBatch() 批量執(zhí)行 SQL,,之后結(jié)束該對象,。這樣,插入操作便完成了,。
另外說明一點,我們這里使用了 ODBC 風(fēng)格的 ? 占位符,,同樣,,我們也可以使用 Oracle 風(fēng)格的占位符:
|
query.prepare("INSERT INTO student (name, age) VALUES (:name, :age)"); |
此時,我們就需要使用
|
query.bindValue(":name", names); query.bindValue(":age", ages); |
進行綁定,。Oracle 風(fēng)格的綁定最大的好處是,,綁定的名字和值很清晰,與順序無關(guān),。但是這里需要注意,,bindValue() 函數(shù)只能綁定一個位置。比如
|
query.prepare("INSERT INTO test (name1, name2) VALUES (:name, :name)"); // ... query.bindValue(":name", name); |
只能綁定第一個 :name 占位符,,不能綁定到第二個,。
接下來我們依舊使用同一個查詢對象執(zhí)行一個 SELECT 語句。如果存在查詢結(jié)果,,QSqlQuery::next() 會返回 true,,直到到達結(jié)果最末,返回 false,,說明遍歷結(jié)束,。我們利用這一點,使用 while 循環(huán)即可遍歷查詢結(jié)果,。使用QSqlQuery::value() 函數(shù)即可按照 SELECT 語句的字段順序獲取到對應(yīng)的數(shù)據(jù)庫存儲的數(shù)據(jù),。
對于數(shù)據(jù)庫事務(wù)的操作,,我們可以使用 QSqlDatabase::transaction() 開啟事務(wù),QSqlDatabase::commit() 或者QSqlDatabase::rollback() 結(jié)束事務(wù),。使用QSqlDatabase::database() 函數(shù)則可以根據(jù)名字獲取所需要的數(shù)據(jù)庫連接,。
|