Qt でデータベースを使う方法(1)

Qt でリレーショナルデータベースを扱う場合は、Qt SQL モジュールを使います。

MySQL や PostgreSQL、SQLITE などのドライバ が用意されており、様々なデータベースを共通のインターフェースで扱うことができるようになっています。

(このうち、QTDS は Qt 4.7 で廃止済み、SQLITE2 も Qt 5.14 で廃止 の予定です。)

今回は、SQLITE3 を利用し、データベースを扱う基本的な方法を紹介します。

ヘッダファイルのインクルード

#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlError>

データベースにアクセスするためには通常上記の3つのクラスを利用します。

データベース接続の作成

QString connectionName("example");
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", connectionName);

データベースの接続は QSqlDatabase クラスで管理され、addDatabase() メソッドで作成します。利用するデータベースのドライバを指定し、後から参照できるように connectionName で接続に名前をつけています。

データベースの接続

db.setDatabaseName(":memory:"); // or file path
if (!db.open()) {
    qWarning() << db.lastError();
}

と、接続するデータベースの情報を指定します。

SQLITE の場合は、setDatabaseName() に、接続するデータベースのファイル名を指定します。

ファイルではなく メモリ上にデータベースを作成する場合は、”:memory:” と指定 することができます。

ここでは、スタンドアロンの SQLITE に接続するため、ホスト名やログインの設定はしていません。

MySQL や PostgreSQL などのデータベースに接続するには、以下のような設定になります。

db.setHostName("localhost");
db.setDatabaseName("test");
db.setUserName("user");
db.setPassword("password");

テーブルの作成

QSqlQuery query(db);
if (query.prepare("CREATE TABLE account("
                  "  id INTEGER PRIMARY KEY"
                  ", name TEXT NOT NULL"
                  ", password TEXT NOT NULL"
                  ")")) {
    if (!query.exec()) {
        qWarning() << query.lastError();
        qInfo() << query.lastQuery() << query.boundValues();
    }
} else {
    qWarning() << query.lastError();
}

テーブルの作成など、SQL の実行には QSqlQuery クラスを利用します。

インスタンスの作成時にデータベースを指定し、prepare() メソッドで SQL を設定し、exec() で実行をしています。

各メソッドが失敗した場合には、lastError() メソッドでエラーの詳細が取得できます。

レコードの追加

QSqlQuery query(db);
if (query.prepare("INSERT INTO account(name, password)"
                  " VALUES (?, ?)")) {
    query.addBindValue("user0");
    query.addBindValue("password0"); // TODO: do not store plain password
    if (query.exec()) {
        qInfo() << query.lastInsertId().toLongLong() << "added";
    } else {
        qWarning() <<query.lastError();
        qInfo() << query.lastQuery() << query.boundValues();
    }
} else {
    qWarning() << query.lastError();
}

SQL のプレースホルダーを利用し、addBindValue() メソッドでデータを指定しています。

トランザクションとレコードの一括追加

if (db.transaction()) {
    QSqlQuery query(db);
    if (query.prepare("INSERT INTO account(name, password)"
                       " VALUES (?, ?)")) {
        for (int i = 1; i < 10; i++) {
            query.addBindValue(QString("user%1").arg(i));
            query.addBindValue(QString("password%1").arg(i));
            if (!query.exec()) {
                qWarning() << query.lastError();
                qInfo() << query.lastQuery() << query.boundValues();
            }
        }
        if (!db.commit()) {
            qWarning() << "commit failed" << db.lastError();
        }
    } else {
        qWarning() << query.lastError();
    }
} else {
    qWarning() << "transaction failed" << db.lastError();
}

トランザクションの制御は transaction()commit()rollback() で行うことが可能です。

複数追加は、prepare() をした後、addBindValue() -> exec() の繰り返しで行っています。

レコードの編集と削除

QSqlQuery query(db);
if (query.prepare("UPDATE account"
                  " SET password = ?"
                  " WHERE name = ?")) {
    query.addBindValue("passwordX");
    query.addBindValue("user7");
    if (!query.exec()) {
        qWarning() << query.lastError();
        qInfo() << query.lastQuery() << query.boundValues();
    }
} else {
    qWarning() << query.lastError();
}

QSqlQuery query(db);
if (query.prepare("DELETE FROM account"
                  " WHERE name = ?")) {
    query.addBindValue("user4");
    if (!query.exec()) {
        qWarning() << query.lastError();
        qInfo() << query.lastQuery() << query.boundValues();
    }
} else {
    qWarning() << query.lastError();
}

prepare(), addBindValue(), exec() の流れは同じです。

レコードの取得

QSqlQuery query(QSqlDatabase::database(connectionName));
if (query.prepare("SELECT id, name, password"
                  " FROM account")) {
    if (query.exec()) {
        while (query.next()) {
            qlonglong id = query.value(0).toLongLong();
            QString name = query.value(1).toString();
            QString password = query.value(2).toString();
            qDebug() << id << name << password;
        }
    } else {
        qWarning() << query.lastError();
        qInfo() << query.lastQuery() << query.boundValues();
    }
} else {
    qWarning() << query.lastError();
}

prepare(), exec() でクエリを発行しています。

WHERE 文などで条件を指定する場合は、プレースホルダを利用し、addBindValue() で値を設定してください。

SQL の実行後に、query.next() でレコードを移動させながら、query.value() でフィールドの値を取得しています。

テーブル一覧の取得

qDebug() << db.tables();

tables() メソッドでデータベースに存在するテーブルの一覧の取得が可能です。

接続の切断

db.close();

接続情報の破棄

QSqlDatabase::removeDatabase(connectionName);

removeDatabase() を実行する際には気をつけるべきことがあります。

削除するものは、connectionName で指定します。また、その接続自体の QSqlDatabase のインスタンスや、それを利用している QSqlQuery のインスタンスが残っている場合にこの処理は失敗します。

このため、実際のコードでは、そういったインスタンスの管理のためのスコープを取り入れるなどの工夫が必要となります。

その辺りを含めた全体のソースコードを https://github.com/task-jp/qsqlite に置きましたので、興味のある方は見てみてください。

おすすめ