#(55):數據庫操作
Qt 提供了 QtSql 模塊來提供平臺獨立的基于 SQL 的數據庫操作。這里我們所說的“平臺獨立”,既包括操作系統平臺,有包括各個數據庫平臺。另外,我們強調了“基于 SQL”,因為 NoSQL 數據庫至今沒有一個通用查詢方法,所以不可能提供一種通用的 NoSQL 數據庫的操作。Qt 的數據庫操作還可以很方便的與 model/view 架構進行整合。通常來說,我們對數據庫的操作更多地在于對數據庫表的操作,而這正是 model/view 架構的長項。
Qt 使用`QSqlDatabase`表示一個數據庫連接。更底層上,Qt 使用驅動(drivers)來與不同的數據庫 API 進行交互。Qt 桌面版本提供了如下幾種驅動:
| ?驅動 | 數據庫 |
| -- || -- |
| QDB2 | IBM DB2 (7.1 或更新版本) |
| QIBASE | Borland InterBase |
| QMYSQL | MySQL |
| QOCI | Oracle Call Interface Driver |
| QODBC | Open Database Connectivity (ODBC) – Microsoft SQL Server 及其它兼容 ODBC 的數據庫 |
| QPSQL | PostgreSQL (7.3 或更新版本) |
| QSQLITE2 | SQLite 2 |
| QSQLITE | SQLite 3 |
| QSYMSQL | 針對 Symbian 平臺的SQLite 3 |
| QTDS | Sybase Adaptive Server (自 Qt 4.7 起廢除) |
不過,由于受到協議的限制,Qt 開源版本并沒有提供上面所有驅動的二進制版本,而僅僅以源代碼的形式提供。通常,Qt 只默認搭載 QSqlite 驅動(這個驅動實際還包括 Sqlite 數據庫,也就是說,如果需要使用 Sqlite 的話,只需要該驅動即可)。我們可以選擇把這些驅動作為 Qt 的一部分進行編譯,也可以當作插件編譯。
如果習慣于使用 SQL 語句,我們可以選擇`QSqlQuery`類;如果只需要使用高層次的數據庫接口(不關心 SQL 語法),我們可以選擇`QSqlTableModel`和`QSqlRelationalTableModel`。本章我們介紹`QSqlQuery`類,在后面的章節則介紹`QSqlTableModel`和`QSqlRelationalTableModel`。
在使用時,我們可以通過
~~~
QSqlDatabase::drivers();
~~~
找到系統中所有可用的數據庫驅動的名字列表。我們只能使用出現在列表中的驅動。由于默認情況下,QtSql 是作為 Qt 的一個模塊提供的。為了使用有關數據庫的類,我們必須早 .pro 文件中添加這么一句:
~~~
QT += sql
~~~
這表示,我們的程序需要使用 Qt 的 core、gui 以及 sql 三個模塊。注意,如果需要同時使用 Qt4 和 Qt5 編譯程序,通常我們的 .pro 文件是這樣的:
~~~
QT += core gui sql
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
~~~
這兩句也很明確:Qt 需要加載 core、gui 和 sql 三個模塊,如果主板本大于 4,則再添加 widgets 模塊。
下面來看一個簡單的函數:
~~~
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()`函數創建一個數據庫連接。我們使用`QSqlDatabase::addDatabase()`靜態函數完成這一請求,也就是創建了一個`QSqlDatabase`實例。注意,數據庫連接使用自己的名字進行區分,而不是數據庫的名字。例如,我們可以使用下面的語句:
~~~
QSqlDatabase db=QSqlDatabase::addDatabase("QSQLITE", QString("con%1").arg(dbName));
~~~
此時,我們是使用`addDatabase()`函數的第二個參數來給這個數據庫連接一個名字。在這個例子中,用于區分這個數據庫連接的名字是`QString("conn%1").arg(dbName)`,而不是 “QSQLITE”。這個參數是可選的,如果不指定,系統會給出一個默認的名字`QSqlDatabase::defaultConnection`,此時,Qt 會創建一個默認的連接。如果你給出的名字與已存在的名字相同,新的連接會替換掉已有的連接。通過這種設計,我們可以為一個數據庫建立多個連接。
我們這里使用的是 sqlite 數據庫,只需要指定數據庫名字即可。如果是數據庫服務器,比如 MySQL,我們還需要指定主機名、端口號、用戶名和密碼,這些語句使用注釋進行了簡單的說明。
接下來我們調用了`QSqlDatabase::open()`函數,打開這個數據庫連接。通過檢查`open()`函數的返回值,我們可以判斷數據庫是不是正確打開。
QtSql 模塊中的類大多具有`lastError()`函數,用于檢查最新出現的錯誤。如果你發現數據庫操作有任何問題,應該使用這個函數進行錯誤的檢查。這一點我們也在上面的代碼中進行了體現。當然,這只是最簡單的實現,一般來說,更好的設計是,不要在數據庫操作中混雜界面代碼(并且將這個`connect()`函數放在一個專門的數據庫操作類中)。
接下來我們可以在`main()`函數中使用這個`connect()`函數:
~~~
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()`函數中,我們調用這個`connect()`函數打開數據庫。如果打開成功,我們通過一個`QSqlQuery`實例執行了 SQL 語句。同樣,我們使用其`lastError()`函數檢查了執行結果是否正確。
注意這里的`QSqlQuery`實例的創建。我們并沒有指定是為哪一個數據庫連接創建查詢對象,此時,系統會使用默認的連接,也就是使用沒有第二個參數的`addDatabase()`函數創建的那個連接(其實就是名字為`QSqlDatabase::defaultConnection`的默認連接)。如果沒有這么一個連接,系統就會報錯。也就是說,如果沒有默認連接,我們在創建`QSqlQuery`對象時必須指明是哪一個`QSqlDatabase`對象,也就是`addDatabase()`的返回值。
我們還可以通過使用`QSqlQuery::isActive()`函數檢查語句執行正確與否。如果`QSqlQuery`對象是活動的,該函數返回 true。所謂“活動”,就是指該對象成功執行了`exec()`函數,但是還沒有完成。如果需要設置為不活動的,可以使用`finish()`或者`clear()`函數,或者直接釋放掉這個`QSqlQuery`對象。這里需要注意的是,如果存在一個活動的 SELECT 語句,某些數據庫系統不能成功完成`connect()`或者`rollback()`函數的調用。此時,我們必須首先將活動的 SELECT 語句設置成不活動的。
創建過數據庫表?student 之后,我們開始插入數據,然后將其獨取出來:
~~~
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;
}
~~~
依舊連接到我們創建的 demo.db 數據庫。我們需要插入多條數據,此時可以使用`QSqlQuery::exec()`函數一條一條插入數據,但是這里我們選擇了另外一種方法:批量執行。首先,我們使用`QSqlQuery::prepare()`函數對這條 SQL 語句進行預處理,問號 ? 相當于占位符,預示著以后我們可以使用實際數據替換這些位置。簡單說明一下,預處理是數據庫提供的一種特性,它會將 SQL 語句進行編譯,性能和安全性都要優于普通的 SQL 處理。在上面的代碼中,我們使用一個字符串列表 names 替換掉第一個問號的位置,一個整型列表 ages 替換掉第二個問號的位置,利用`QSqlQuery::addBindValue()`我們將實際數據綁定到這個預處理的 SQL 語句上。需要注意的是,names 和 ages 這兩個列表里面的數據需要一一對應。然后我們調用`QSqlQuery::execBatch()`批量執行 SQL,之后結束該對象。這樣,插入操作便完成了。
另外說明一點,我們這里使用了 ODBC 風格的 ? 占位符,同樣,我們也可以使用 Oracle 風格的占位符:
~~~
query.prepare("INSERT INTO student (name, age) VALUES (:name, :age)");
~~~
此時,我們就需要使用
~~~
query.bindValue(":name", names);
query.bindValue(":age", ages);
~~~
進行綁定。Oracle 風格的綁定最大的好處是,綁定的名字和值很清晰,與順序無關。但是這里需要注意,`bindValue()`函數只能綁定一個位置。比如
~~~
query.prepare("INSERT INTO test (name1, name2) VALUES (:name, :name)");
// ...
query.bindValue(":name", name);
~~~
只能綁定第一個 :name 占位符,不能綁定到第二個。
接下來我們依舊使用同一個查詢對象執行一個 SELECT 語句。如果存在查詢結果,`QSqlQuery::next()`會返回 true,直到到達結果最末,返回 false,說明遍歷結束。我們利用這一點,使用 while 循環即可遍歷查詢結果。使用`QSqlQuery::value()`函數即可按照 SELECT 語句的字段順序獲取到對應的數據庫存儲的數據。
對于數據庫事務的操作,我們可以使用?`QSqlDatabase::transaction()`?開啟事務,`QSqlDatabase::commit()`?或者`QSqlDatabase::rollback()`?結束事務。使用`QSqlDatabase::database()`函數則可以根據名字獲取所需要的數據庫連接。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)