C++ Qt開發:QSqlDatabase資料庫元件

lyshark發表於2023-12-26

Qt 是一個跨平臺C++圖形介面開發庫,利用Qt可以快速開發跨平臺窗體應用程式,在Qt中我們可以透過拖拽的方式將不同元件放到指定的位置,實現圖形化開發極大的方便了開發效率,本章將重點介紹QSqlDatabase資料庫模組的常用方法及靈活運用。

Qt SQL模組是Qt框架的一部分,它提供了一組類和函式,用於在Qt應用程式中進行資料庫操作。這個模組的目標是簡化資料庫訪問和操作,並提供一致的介面,使得開發者可以方便地與不同資料庫系統進行互動。一般SQL元件常用的操作,包括讀取資料、插入資料、更新資料、刪除資料功能,這四個功能我將分別介紹它是如何使用的。

在使用此模組時必須要引入對應檔案,需要在*.pro檔案內增加QT += sql,並在標頭檔案內匯入QSqlDatabase模組才可以正常使用,該模組是用於管理資料庫連線的核心類之一。它提供了一系列方法,使得在Qt應用程式中進行資料庫操作變得方便和靈活。QSqlDatabase類的靈活性使得開發者能夠與多種資料庫系統(如SQLite、MySQL、PostgreSQL等)進行互動,而不必擔心底層資料庫細節。這有助於實現跨資料庫的可移植性和更高層次的資料庫訪問抽象。

下面是QSqlDatabase類中一些常用的方法,以表格形式進行說明和概述:

方法 描述
QSqlDatabase::addDatabase(const QString &type, const QString &connectionName = QLatin1String(defaultConnection)) 新增一個資料庫連線,其中type指定資料庫驅動型別,connectionName指定連線的名稱,預設為預設連線。返回建立的資料庫連線物件。
QSqlDatabase::database(const QString &connectionName = QLatin1String(defaultConnection), bool open = true) 獲取指定連線名稱的資料庫連線物件。如果資料庫連線不存在,會建立一個新的連線。如果opentrue,則嘗試開啟資料庫連線。
QSqlDatabase::removeDatabase(const QString &connectionName = QLatin1String(defaultConnection)) 移除指定連線名稱的資料庫連線。如果該連線當前處於開啟狀態,則會被關閉。
QSqlDatabase::setHostName(const QString &host) 設定資料庫伺服器的主機名。
QSqlDatabase::setDatabaseName(const QString &name) 設定要連線的資料庫的名稱。
QSqlDatabase::setUserName(const QString &name) 設定用於連線資料庫的使用者名稱。
QSqlDatabase::setPassword(const QString &password) 設定用於連線資料庫的密碼。
QSqlDatabase::setPort(int port) 設定資料庫伺服器的埠號。
QSqlDatabase::open() 開啟資料庫連線。如果連線成功,返回true,否則返回false
QSqlDatabase::close() 關閉資料庫連線。
QSqlDatabase::isOpen() 判斷資料庫連線是否開啟。返回true表示連線已開啟,false表示連線未開啟。
QSqlDatabase::tables(QSql::TableType type = QSql::Tables) 返回資料庫中的表的列表。可以指定表的型別,如QSql::Tables表示使用者表,QSql::SystemTables表示系統表。
QSqlDatabase::commit() 提交當前事務。
QSqlDatabase::rollback() 回滾當前事務。
QSqlDatabase::transaction() 開始一個新事務。

這些方法提供了管理和運算元據庫連線的基本功能,包括連線資料庫、設定連線引數、開啟和關閉連線、執行事務等。在實際使用中,開發者可以根據需要選擇適當的方法來管理資料庫連線和執行資料庫操作。

1.1 逐條記錄插入

初始化資料庫我們可以透過呼叫QSqlDatabase::addDatabase來開啟,在開啟引數中支援多種資料庫型別的選擇,包括但不限於 SQLiteMySQLPostgreSQLOracleODBC 等,每種資料庫型別對應一個特定的驅動,開發者可以透過指定資料庫型別和連線名稱建立相應的資料庫連線。

QSqlDatabase::addDatabase 是一個靜態方法,用於嚮應用程式中新增一個資料庫連線。此方法允許你為不同的資料庫型別新增連線,並且你可以為每個連線指定一個唯一的名稱。

static QSqlDatabase QSqlDatabase::addDatabase(const QString &type, const QString &connectionName = QLatin1String(defaultConnection));

引數說明

  • type: 字串,表示資料庫的型別,例如 "QSQLITE"、"QMYSQL" 等,具體取決於你所使用的資料庫驅動。
  • connectionName: 字串,表示連線的名稱,預設為 defaultConnection。可以透過這個名稱在應用程式中區分不同的資料庫連線。

返回值

返回建立的 QSqlDatabase 物件,可以使用這個物件進行進一步的資料庫配置和操作。如果不提供連線名稱,將使用預設的連線名稱 defaultConnection。在一個應用程式中,你可以同時擁有多個資料庫連線,每個連線都有一個唯一的名稱。

在開啟後接著我們就可以透過執行db.exec()的方式向特定資料庫內插入資料,如下程式碼所示,透過在編輯框內提取出所需引數並對資料庫進行初始化,當出事後成功後則呼叫db.exec()函式插入記錄,最後透過db.commit()提交事務重新整理到資料庫中。

void MainWindow::on_pushButton_clicked()
{
    // 指定資料庫驅動型別
    QSqlDatabase db = QSqlDatabase::addDatabase(ui->lineEdit_type->text());
    db.setDatabaseName(ui->lineEdit_dir->text());
     if (!db.open())
     {
            QMessageBox::information(nullptr, "資訊", db.lastError().text(), QMessageBox::Ok);
     }

     // 獲取文字內容
     QString plainText = ui->plainTextEdit->toPlainText();

     // 使用 split() 函式分割成行
     QStringList lines = plainText.split('\n', Qt::SkipEmptyParts);

     // 遍歷每一行
     for (const QString &line : lines)
     {
         db.exec(line);
     }

    // 提交事務請求
    bool ref = db.commit();
    if(ref == true)
    {
        QMessageBox::information(nullptr, "資訊", "初始化失敗", QMessageBox::Ok);
    }
    else
    {
        QMessageBox::information(nullptr, "資訊", "執行初始化成功", QMessageBox::Ok);
    }

    db.close();
}

在初始化部分,我們透過create table語句建立一個LyShark的資料表,並插入三個欄位,分別是id,name,age,當點選初始化時則會使用QSQLITE引擎,在當前目錄下生成一個名為database.sqlite的資料庫檔案;

同理,只要準備合理的SQL語句就可以實現對應的資料庫記錄的插入功能;

1.2 多條記錄插入

多條記錄的插入依賴於QSqlQuery類,該類是Qt中用於執行和處理SQL查詢的類。它允許你向資料庫傳送 SQL 查詢並檢索查詢結果。以下是QSqlQuery類中一些常用的方法,以表格形式進行說明和概述:

方法 描述
QSqlQuery::QSqlQuery(QSqlDatabase db = QSqlDatabase()) 建構函式,建立一個資料庫查詢物件。如果提供了資料庫連線物件 db,則該查詢物件將與指定的資料庫連線關聯。
QSqlQuery::~QSqlQuery() 解構函式,釋放查詢物件。在物件銷燬時,會自動關閉查詢。
exec(const QString &query) 執行指定的 SQL 查詢。返回 true 表示執行成功,false 表示執行失敗。
execBatch(BatchExecutionMode mode = ValuesAsRows) 批次執行多個 SQL 查詢。可以設定批次執行模式。
isActive() const 判斷查詢是否處於活動狀態(已執行並且未關閉)。
isSelect() const 判斷當前查詢是否是 SELECT 查詢。
lastError() const 獲取最後一次執行的查詢的錯誤資訊。如果查詢成功,返回一個空的 QSqlError 物件。
next() 移動到結果集中的下一條記錄。返回 true 表示移動成功,false 表示已經沒有更多記錄。
seek(int index, bool relative = false) 定位到結果集中的指定記錄。
value(int index) 獲取當前記錄中指定列的值。
value(const QString &name) 獲取當前記錄中指定列名的值。
prepare(const QString &query) 準備一個 SQL 查詢。可以在查詢中使用佔位符 ? 作為引數的佔位符。
bindValue(const QString &placeholder, const QVariant &val, QSql::ParamType type = QSql::In) 繫結引數到查詢。可以使用佔位符 ? 或者命名佔位符 :name
executedQuery() const 獲取實際執行的 SQL 查詢。當使用佔位符時,這個方法返回實際執行的 SQL 語句。
record() const 獲取查詢的後設資料資訊,包括欄位名、欄位型別等。
size() const 獲取結果集的記錄數。

這些方法提供了一些基本的資料庫查詢和結果處理功能,包括執行查詢、處理結果、錯誤處理、繫結引數、獲取後設資料等。在實際應用中,可以根據具體的需求選擇適當的方法來運算元據庫。

如下程式碼所示,首先我們透過split的方式將兩個plainEditText中的內容進行分割,並分別將結果儲存至QStringList容器內,接著透過使用query.prepare繫結一個SQL語句對應關係,並透過迴圈的方式以此插入資料,程式碼如下所示;

void MainWindow::on_pushButton_3_clicked()
{
    // 指定資料庫驅動型別
    QSqlDatabase db = QSqlDatabase::addDatabase(ui->lineEdit_type->text());
    db.setDatabaseName(ui->lineEdit_dir->text());
    if (!db.open())
    {
        QMessageBox::information(nullptr, "資訊", db.lastError().text(), QMessageBox::Ok);
    }

    // 定義字串連結串列
    QStringList user_name;
    QStringList user_age;

    // 獲取文字內容
    QString plainText_uname = ui->plainTextEdit_uname->toPlainText();
    QString plainTextEdit_uage = ui->plainTextEdit_uage->toPlainText();

    // 使用 split() 函式分割成行
    QStringList lines_uname = plainText_uname.split('\n', Qt::SkipEmptyParts);
    QStringList lines_uage = plainText_uname.split('\n', Qt::SkipEmptyParts);

    // 遍歷每一行
    for (const QString &line : lines_uname)
    {
        user_name.append(line);
    }

    for (const QString &line : lines_uage)
    {
        user_age.append(line);
    }

    // 繫結資料記錄
    QSqlQuery query;
    query.prepare("INSERT INTO LyShark(name,age) ""VALUES (:name, :age)");

    // 判斷兩張表中欄位資料量是否一致
    if(user_name.size() == user_age.size())
    {
        // 迴圈插入資料
        for(int x=0;x< user_name.size(); x++)
        {
            query.bindValue(":name",user_name[x]);
            query.bindValue(":age",user_age[x]);
            query.exec();
        }
    }

    // 提交事務請求
    bool ref = db.commit();
    if(ref == true)
    {
        QMessageBox::information(nullptr, "資訊", "插入資料失敗", QMessageBox::Ok);
    }
    else
    {
        QMessageBox::information(nullptr, "資訊", "插入資料成功", QMessageBox::Ok);
    }

    db.close();
}

執行後則可以將如下所示的欄位依次插入到資料庫中儲存,如下圖所示;

1.3 查詢表中記錄

查詢表中記錄離不開QSqlRecord 類,它是Qt中用於表示資料庫記錄(行)的後設資料的類。提供了關於記錄中欄位(列)的資訊,包括欄位名、欄位型別等。通常用於表示資料庫查詢的結果集中的一行記錄的後設資料,以便在程式中處理這些記錄的資訊。

以下是QSqlRecord類中一些常用的方法,以表格形式進行說明和概述:

方法 描述
QSqlRecord::QSqlRecord(const QSqlRecord &other) 複製建構函式,建立一個 QSqlRecord 物件,複製另一個記錄的資訊。
QSqlRecord::~QSqlRecord() 解構函式,釋放 QSqlRecord 物件。
append(const QSqlField &field) 向記錄中新增一個欄位。
clear() 清空記錄中的所有欄位。
field(int index) const 獲取指定索引的欄位資訊。
field(const QString &name) const 獲取指定欄位名的欄位資訊。
fieldName(int index) const 獲取指定索引的欄位名。
indexOf(const QString &name) const 獲取指定欄位名的索引。如果欄位不存在,返回 -1。
isEmpty() const 判斷記錄是否為空(沒有欄位)。
isGenerated(int index) const 判斷指定索引的欄位是否為自動生成的。
setGenerated(int index, bool generated) 設定指定索引的欄位是否為自動生成的。
setGenerated(const QString &name, bool generated) 設定指定欄位名的欄位是否為自動生成的。
setField(int index, const QSqlField &field) 設定指定索引的欄位資訊。
count() const 獲取記錄中欄位的數量。
contains(const QString &name) const 判斷記錄中是否包含指定欄位名的欄位。
operator=() 賦值運算子過載,將一個記錄的內容複製給另一個記錄。

這些方法提供了一些基本的記錄處理功能,包括新增欄位、獲取欄位資訊、設定欄位資訊、判斷欄位是否存在等。在實際應用中,可以根據具體的需求選擇適當的方法來操作記錄。

在查詢資料時,我們只需要透過QSqlQuery得到完整的資料表記錄條數,然後就可以使用QSqlRecord來建立一個查詢物件,此時每次呼叫query.next()都會向後遍歷一行記錄,透過rec.indexOf就可以得到對應欄位的引數值,而query.value則可以將其提取出來,最後我們將其插入到TreeWidget中用於展示,程式碼如下所示;

// 查詢表中資料
void MainWindow::on_pushButton_4_clicked()
{
    // 指定資料庫驅動型別
    QSqlDatabase db = QSqlDatabase::addDatabase(ui->lineEdit_type->text());
    db.setDatabaseName(ui->lineEdit_dir->text());
    if (!db.open())
    {
        QMessageBox::information(nullptr, "資訊", db.lastError().text(), QMessageBox::Ok);
    }

    // 查詢資料
    QSqlQuery query("SELECT * FROM LyShark;",db);
    QSqlRecord rec = query.record();

    // 迴圈所有記錄
    while(query.next())
    {
        // 判斷當前記錄是否有效
        if(query.isValid())
        {
            // 讀出資料
            int id_ptr = rec.indexOf("id");
            int id_value = query.value(id_ptr).toInt();

            int name_ptr = rec.indexOf("name");
            QString name_value = query.value(name_ptr).toString();

            int age_ptr = rec.indexOf("age");
            int age_value = query.value(age_ptr).toInt();

            // 設定treeWidget屬性
            ui->treeWidget->setColumnCount(3);         // 設定總列數
            ui->treeWidget->setColumnWidth(0,300);     // 設定最後一列寬度自適應
            ui->treeWidget->setIndentation(0);         // 設定表頭縮排為0

            // 設定表頭資料
            QStringList headers;
            headers.append("UID");
            headers.append("使用者名稱");
            headers.append("年齡");

            ui->treeWidget->setHeaderLabels(headers);

            // 模擬插入資料到表中
            QTreeWidgetItem* item=new QTreeWidgetItem();
            item->setText(0,QString::number(id_value));
            item->setText(1,name_value);
            item->setText(2,QString::number(age_value));
            ui->treeWidget->addTopLevelItem(item);
        }
    }
}

編譯並執行程式,當點選查詢按鈕時,則可以將資料庫中的資料輸出到元件中顯示,如下圖所示;

1.5 更新表中記錄

最後一項是對記錄的更新,其實更新記錄同樣是使用exec()函式,只不過是將插入語句修改為了update而已,如下程式碼透過資料庫查詢並根據特定條件填充了介面上的兩個文字框 (ui->lineEdit_select_unameui->lineEdit_select_uage)。下面是這段程式碼的概述:

  1. 建立資料庫連線:
    • 透過 QSqlDatabase::addDatabase 設定資料庫的驅動型別,例如 "QSQLITE"、"QMYSQL" 等。
    • 使用 setDatabaseName 設定資料庫名稱,這可能是一個本地檔名或者伺服器地址。
    • 嘗試開啟資料庫連線,如果連線失敗,透過 QMessageBox 顯示錯誤資訊。
  2. 執行資料庫查詢:
    • 使用 QSqlQuery 物件執行 SQL 查詢語句 "SELECT * FROM LyShark;"。
    • 透過 QSqlRecord 獲取查詢結果的記錄結構,包括欄位名和欄位型別。
  3. 迴圈處理查詢結果:
    • 使用 while (query.next()) 迴圈遍歷查詢結果中的每一行記錄。
    • 對於每個有效的記錄,獲取 "id" 欄位的值,並與使用者輸入的 "uid" 進行匹配。
    • 如果匹配成功,獲取 "name" 和 "age" 欄位的值,並將其分別填充到 ui->lineEdit_select_unameui->lineEdit_select_uage 中。
    • 如果沒有匹配的記錄,或者記錄無效,將 ui->lineEdit_select_unameui->lineEdit_select_uage 的文字設定為 "-1"。

這段程式碼主要完成了從資料庫查詢資料並將結果填充到使用者介面的操作。需要注意的是,如果涉及使用者輸入的 ui->lineEdit_select_uid->text() 不是數字,可能需要額外的驗證和處理。此外,資料庫的表結構和欄位名需要與程式碼中的對應關係一致。

void MainWindow::on_pushButton_5_clicked()
{
    // 指定資料庫驅動型別
    QSqlDatabase db = QSqlDatabase::addDatabase(ui->lineEdit_type->text());
    db.setDatabaseName(ui->lineEdit_dir->text());
    if (!db.open())
    {
        QMessageBox::information(nullptr, "資訊", db.lastError().text(), QMessageBox::Ok);
    }

    // 查詢資料
    QSqlQuery query("SELECT * FROM LyShark;",db);
    QSqlRecord rec = query.record();

    // 迴圈所有記錄
    while(query.next())
    {
        // 判斷當前記錄是否有效
        if(query.isValid())
        {
            // 讀出資料
            int id_ptr = rec.indexOf("id");
            int id_value = query.value(id_ptr).toInt();

            // 如果是則填充表格
            if(QString::number(id_value) == ui->lineEdit_select_uid->text())
            {
                int name_ptr = rec.indexOf("name");
                QString name_value = query.value(name_ptr).toString();
                ui->lineEdit_select_uname->setText(QString(name_value.data()));

                int age_ptr = rec.indexOf("age");
                QString age_value = query.value(age_ptr).toString();
                ui->lineEdit_select_uage->setText(QString(age_value.data()));
            }
        }
        else
        {
            ui->lineEdit_select_uname->setText("-1");
            ui->lineEdit_select_uage->setText("-1");
        }
    }
}

讀者可透過輸入一個唯一的識別符號,例如UID號,來實現對特定資料的查詢功能,如下圖所示;

資料的跟新只需要呼叫update語句即可實現,其他的功能與查詢保持一致,如下程式碼實現了資料的共噁心操作,以下是程式碼的概述:

  1. 建立資料庫連線:
    • 使用 QSqlDatabase::addDatabase 設定資料庫的驅動型別,例如 "QSQLITE"、"QMYSQL" 等。
    • 使用 setDatabaseName 設定資料庫名稱,這可能是一個本地檔名或者伺服器地址。
    • 嘗試開啟資料庫連線,如果連線失敗,透過 QMessageBox 顯示錯誤資訊。
  2. 執行資料庫更新:
    • 從使用者介面的輸入框中獲取更新所需的資料,包括 uidname、和 age
    • 構建 SQL 更新語句,例如 UPDATE LyShark SET name='newName', age=25 WHERE id=123;
    • 使用 db.exec(sql) 執行 SQL 更新語句。
  3. 事務的提交和關閉:
    • 嘗試提交事務,如果成功,顯示更新資料成功的訊息,否則顯示更新資料失敗的訊息。
    • 關閉資料庫連線。

需要注意:

  • 在一般情況下,Qt 的資料庫操作會自動處理事務,你不必顯式呼叫 commit()
  • 使用 std::cout 輸出日誌不太符合 Qt 的風格,Qt 提供了 qDebug() 用於輸出除錯資訊。
  • 對於事務,通常在更新操作後關閉資料庫連線,而不是在提交事務之前。
void MainWindow::on_pushButton_6_clicked()
{
    // 指定資料庫驅動型別
    QSqlDatabase db = QSqlDatabase::addDatabase(ui->lineEdit_type->text());
    db.setDatabaseName(ui->lineEdit_dir->text());
    if (!db.open())
    {
        QMessageBox::information(nullptr, "資訊", db.lastError().text(), QMessageBox::Ok);
    }

    // 執行SQL更新記錄
    int uid = ui->lineEdit_select_uid->text().toInt();
    QString name = ui->lineEdit_select_uname->text();
    int age = ui->lineEdit_select_uage->text().toInt();

    QString sql = QString("UPDATE LyShark SET name='%1', age=%2 WHERE id=%3;").arg(name).arg(age).arg(uid);
    db.exec(sql);
    std::cout << "update => " << sql.toStdString() << std::endl;

    // 提交事務請求
    bool ref = db.commit();
    if(ref == true)
    {
        QMessageBox::information(nullptr, "資訊", "更新資料失敗", QMessageBox::Ok);
    }
    else
    {
        QMessageBox::information(nullptr, "資訊", "更新資料成功", QMessageBox::Ok);
    }

    db.close();
}

讀者可透過輸入一個UID編號查詢資料記錄,接著在修改對應的欄位值,並點選更新按鈕重新整理資料庫,如下圖所示將第一個記錄的姓名重新整理為lyshark

相關文章