1. 単一テーブルの基本的なデータベース操作
QtフレームワークのSQLモジュールは、SQLite、MySQL、PostgreSQLなど、様々なデータベースとの連携を可能にします。ここでは、QtのQSQLITEドライバーを使用して、単一のデータベースファイルとテーブルを扱う基本的な手順を説明します。
データベース操作のフロー
- データベース接続を確立し、オープンする。
- データベース内にテーブルが存在しない場合、作成する。
- テーブルからデータを取得し、結果を反復処理する。
- データベース接続をクローズする。
以下のC++コードは、SQLiteデータベースファイル app_records.db を作成し、blast_events というテーブルを管理する例です。テーブルが存在しない場合は新しく作成し、その後データをクエリして結果を処理します。
// データベース接続の確立とオープン
QSqlDatabase dbConnection = QSqlDatabase::addDatabase("QSQLITE");
dbConnection.setDatabaseName("app_records.db"); // データベースファイル名を設定
if (!dbConnection.open()) {
qDebug() << "エラー: データベースへの接続に失敗しました。" << dbConnection.lastError();
// ここでアプリケーションの終了など、適切なエラー処理を行う
return;
}
// QSqlQueryオブジェクトの準備
QSqlQuery dbQuery(dbConnection);
// テーブルの作成(存在しない場合)
QString createTableSql = "CREATE TABLE IF NOT EXISTS blast_events ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"event_date TEXT, "
"event_count INTEGER, "
"status_code INTEGER, "
"record_id INTEGER)";
if (!dbQuery.exec(createTableSql)) {
qDebug() << "エラー: テーブル 'blast_events' の作成に失敗しました。" << dbQuery.lastError();
} else {
qDebug() << "テーブル 'blast_events' が準備できました。";
}
// データの挿入例 (必要に応じてコメント解除して使用)
/*
dbQuery.prepare("INSERT INTO blast_events (event_date, event_count, status_code, record_id) VALUES (?, ?, ?, ?)");
dbQuery.bindValue(0, "2023-11-01");
dbQuery.bindValue(1, 10);
dbQuery.bindValue(2, 0); // ステータスコード
dbQuery.bindValue(3, 201);
if (!dbQuery.exec()) {
qDebug() << "データの挿入に失敗しました。" << dbQuery.lastError();
} else {
qDebug() << "新しいレコードが挿入されました。";
}
*/
// テーブルからのデータ読み込みと処理
int inactiveRecordsCount = 0;
if (dbQuery.exec("SELECT id, event_date, event_count, status_code, record_id FROM blast_events")) {
while (dbQuery.next()) {
int recordId = dbQuery.value("id").toInt();
QString date = dbQuery.value("event_date").toString();
int count = dbQuery.value("event_count").toInt();
int status = dbQuery.value("status_code").toInt();
int associatedRecordId = dbQuery.value("record_id").toInt();
// status_code が 1 ではないレコードを特定
if (status != 1) {
inactiveRecordsCount++;
qDebug() << "非アクティブなレコードが見つかりました: ID=" << recordId
<< ", 日付=" << date << ", ステータス=" << status;
}
// ここで取得したデータをアプリケーションのロジックで利用できます
}
qDebug() << "処理された非アクティブなレコードの総数: " << inactiveRecordsCount;
} else {
qDebug() << "エラー: データ取得に失敗しました。" << dbQuery.lastError();
}
// データベース接続のクローズ
dbConnection.close();
qDebug() << "データベース接続がクローズされました。";
2. 複数のテーブルを管理するデータベース設計
実際のアプリケーションでは、単一のデータベースファイル内に複数のテーブルを持つことが一般的です。これを効果的に管理するために、データベース接続とテーブル作成ロジックをカプセル化したシングルトンデザインパターンを持つクラスを作成することが推奨されます。
以下の例では、DatabaseManager というクラスがデータベースへの単一のエントリポイントを提供し、アプリケーション起動時に必要なすべてのテーブルが存在することを確認します。これにより、複数の箇所でデータベース接続やテーブル作成ロジックを重複させることを避け、保守性を向上させます。
#include <QSqlRecord>
#include <QSqlDriver>
#include <QSqlField>
#include <QMutex>
#include <QDebug>
#include <QStringList> // QStringList が必要
// データベース接続設定用の定数
#define DB_CONNECTION_NAME "application_db_session"
#define DB_FILE_NAME "application_data.db"
#define DB_DRIVER "QSQLITE"
// データベースを管理するシングルトンクラス
class DatabaseManager : public QObject
{
Q_OBJECT
public:
static DatabaseManager* instance(); // シングルトンインスタンスを取得
~DatabaseManager(); // デストラクタ
QSqlDatabase getDatabase() const { return m_database; } // データベース接続オブジェクトを返す
// 雷管メッセージ構造体(元のコードのLGMessageDefを模倣)
struct DetonatorMessage {
long long Uid; // ユニークID
int DT; // 遅延時間 (Delay Time)
int KW; // キーワード (エリアとホール情報を含む)
int Det_status; // 雷管ステータスフラグ
int DesignDT; // 設計遅延時間
int DesignKW; // 設計キーワード
};
// 雷管ステータスフラグの定義
enum DetonatorStatusFlags {
DesignFlag = 0x01, // 設計済み
AuthFlag = 0x02 // 認証済み
};
// 雷管パラメータを処理し、データベースに保存するメソッド
void processDetonatorParameters(DetonatorMessage* messages, int count);
private:
explicit DatabaseManager(QObject *parent = nullptr); // コンストラクタ
static DatabaseManager* m_instance; // シングルトンインスタンス
static QMutex m_mutex; // スレッドセーフのためのミューテックス
QSqlDatabase m_database; // データベース接続オブジェクト
// ヘルパー関数: UIDを文字列に変換
QString convertDetonatorIdToString(long long uid);
// ヘルパー関数: テーブルが存在しない場合に作成
void ensureTableExists(QSqlQuery& query, const QString& tableName, const QString& createSql);
};
// シングルトンインスタンスの初期化
DatabaseManager* DatabaseManager::m_instance = nullptr;
QMutex DatabaseManager::m_mutex;
// シングルトンインスタンスを取得する静的メソッド
DatabaseManager* DatabaseManager::instance()
{
if (!m_instance) {
QMutexLocker locker(&m_mutex); // スレッドセーフなインスタンス生成
if (!m_instance) {
m_instance = new DatabaseManager();
}
}
return m_instance;
}
// コンストラクタ: データベース接続を確立し、必要なテーブルを作成
DatabaseManager::DatabaseManager(QObject *parent) : QObject(parent)
{
if (QSqlDatabase::contains(DB_CONNECTION_NAME)) {
m_database = QSqlDatabase::database(DB_CONNECTION_NAME);
} else {
m_database = QSqlDatabase::addDatabase(DB_DRIVER, DB_CONNECTION_NAME);
m_database.setDatabaseName(DB_FILE_NAME);
}
if (!m_database.isOpen()) {
if (!m_database.open()) {
qDebug() << "エラー: データベースのオープンに失敗しました。" << m_database.lastError();
// 致命的なエラーのため、適切なエラーハンドリングが必要
}
}
QSqlQuery dbQuery(m_database);
// 各テーブルが存在するか確認し、なければ作成する
ensureTableExists(dbQuery, "system_configurations",
"CREATE TABLE system_configurations(id INTEGER PRIMARY KEY, brightness INTEGER, backlight INTEGER, timestamp DATETIME, "
"longitude VARCHAR(5), latitude VARCHAR(5), location_time DATETIME, style INTEGER, key_sound INTEGER, charge_level INTEGER, "
"can_switch INTEGER, mA1_data INTEGER, mA2_data INTEGER, uA_data INTEGER, LG00_enabled INTEGER, org_number INTEGER, "
"status_check INTEGER, page_style INTEGER, needs_auth INTEGER, needs_gps INTEGER, auth_number INTEGER, "
"device_id1 INTEGER, device_id2 INTEGER, device_id3 INTEGER, device_id4 INTEGER, device_id5 INTEGER, "
"software_version INTEGER, device_status INTEGER)");
ensureTableExists(dbQuery, "detonator_delay_settings",
"CREATE TABLE detonator_delay_settings(id INTEGER PRIMARY KEY AUTOINCREMENT, detonator_code TEXT UNIQUE, delay_time INTEGER, "
"area_id INTEGER, hole_id INTEGER, auth_status INTEGER)");
ensureTableExists(dbQuery, "event_logs",
"CREATE TABLE event_logs(id INTEGER PRIMARY KEY AUTOINCREMENT, event_date TEXT, "
"count INTEGER, status INTEGER, blast_record_id INTEGER, longitude TEXT, latitude TEXT, "
"bscan_mA INTEGER, bscan_mV INTEGER, scanning_mA INTEGER, scanning_mV INTEGER, "
"bcharge_mA INTEGER, bcharge_mV INTEGER, charging_mA INTEGER, charging_mV INTEGER, "
"charged_mA INTEGER, charged_mV INTEGER, bboom_mA INTEGER, bboom_mV INTEGER, bat_voltage INTEGER)");
ensureTableExists(dbQuery, "record_identifiers",
"CREATE TABLE record_identifiers(id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT, time INTEGER, blast_record_id INTEGER)");
ensureTableExists(dbQuery, "authorization_headers",
"CREATE TABLE authorization_headers(id INTEGER PRIMARY KEY AUTOINCREMENT, esp_data TEXT, "
"auth_id TEXT, longitude VARCHAR(5), latitude VARCHAR(5), "
"radius INTEGER, start_time TEXT, stop_time TEXT, lg_number INTEGER, "
"useful_time INTEGER, lg_range_number INTEGER, password_number INTEGER, download_data TEXT)");
ensureTableExists(dbQuery, "authorization_passwords",
"CREATE TABLE authorization_passwords(id INTEGER PRIMARY KEY AUTOINCREMENT, lg_password TEXT, auth_file_id INTEGER)");
ensureTableExists(dbQuery, "user_accounts",
"CREATE TABLE user_accounts(user_id INTEGER PRIMARY KEY AUTOINCREMENT, org_id VARCHAR(32), "
"real_name NVARCHAR(32), org_name NVARCHAR(32), login_name VARCHAR(32), user_password VARCHAR(32))");
qDebug() << "データベースマネージャーが初期化されました。";
}
// デストラクタ: データベース接続をクローズ
DatabaseManager::~DatabaseManager()
{
if (m_database.isOpen()) {
m_database.close();
QSqlDatabase::removeDatabase(DB_CONNECTION_NAME);
}
// シングルトンインスタンスはアプリケーション終了時にOSによってクリーンアップされる
}
// ヘルパー関数: テーブルの存在を確認し、なければ作成
void DatabaseManager::ensureTableExists(QSqlQuery& query, const QString& tableName, const QString& createSql)
{
if (!m_database.tables().contains(tableName)) {
if (!query.exec(createSql)) {
qDebug() << "エラー: テーブル '" << tableName << "' の作成に失敗しました。" << query.lastError();
} else {
qDebug() << "テーブル '" << tableName << "' を作成しました。";
}
}
}
// ヘルパー関数: 雷管UIDを文字列に変換(仮の実装)
QString DatabaseManager::convertDetonatorIdToString(long long uid)
{
// 実際のアプリケーションでは、UIDの具体的な形式に応じて変換ロジックを実装
return QString::number(uid);
}
// 雷管パラメータを処理し、データベースに保存するメソッド
void DatabaseManager::processDetonatorParameters(DetonatorMessage* messages, int count)
{
QSqlQuery dbQuery(m_database);
for (int i = 0; i < count; ++i) {
DetonatorMessage* currentMsg = messages + i;
// 設計済みフラグが立っている場合は処理をスキップ
if (currentMsg->Det_status & DesignFlag) {
continue;
}
QString detonatorId = convertDetonatorIdToString(currentMsg->Uid);
int delayTime = currentMsg->DT;
int areaId = (currentMsg->KW >> 10); // KWの上位ビットからエリアIDを抽出
int holeId = (currentMsg->KW & 0x3FF); // KWの下位ビットからホールIDを抽出
int authStatus = (currentMsg->Det_status & AuthFlag) ? 1 : 0; // 認証済みなら1、そうでなければ0
// 同じdetonator_codeを持つレコードが既に存在するか確認
dbQuery.prepare("SELECT id FROM detonator_delay_settings WHERE detonator_code = ?");
dbQuery.bindValue(0, detonatorId);
if (!dbQuery.exec()) {
qDebug() << "エラー: レコード存在チェックに失敗しました。" << dbQuery.lastError();
continue; // 次のメッセージへ
}
if (!dbQuery.next()) { // レコードが存在しない場合のみ挿入
dbQuery.prepare("INSERT INTO detonator_delay_settings (detonator_code, delay_time, area_id, hole_id, auth_status) "
"VALUES (?, ?, ?, ?, ?)");
dbQuery.bindValue(0, detonatorId);
dbQuery.bindValue(1, delayTime);
dbQuery.bindValue(2, areaId);
dbQuery.bindValue(3, holeId);
dbQuery.bindValue(4, authStatus);
if (!dbQuery.exec()) {
qDebug() << "エラー: 遅延パラメータの挿入に失敗しました。" << dbQuery.lastError();
} else {
// 挿入成功後、メッセージオブジェクトの設計済み状態を更新
currentMsg->DesignDT = currentMsg->DT;
currentMsg->DesignKW = currentMsg->KW;
currentMsg->Det_status |= DesignFlag; // 設計済みフラグを設定
qDebug() << "新しい遅延パラメータが挿入されました: ID " << detonatorId;
}
} else {
// レコードが既に存在する場合、ここでは何もしない(元のロジックに倣う)
// 必要であれば、ここでUPDATE処理などを実装することも可能
qDebug() << "遅延パラメータは既に存在します: ID " << detonatorId;
}
}
}
// メイン関数での使用例 (完全なMOC処理のためには適切なプロジェクト設定が必要)
/*
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// DatabaseManagerのインスタンスを取得
DatabaseManager* dbManager = DatabaseManager::instance();
QSqlDatabase currentDb = dbManager->getDatabase();
// 例として雷管メッセージデータを作成
DatabaseManager::DetonatorMessage messages[2];
messages[0] = {1001, 100, (1 << 10) | 50, 0, 0, 0}; // Area 1, Hole 50, 未設計
messages[1] = {1002, 120, (2 << 10) | 60, DatabaseManager::DesignFlag, 0, 0}; // Area 2, Hole 60, 設計済み
// 雷管パラメータを処理
dbManager->processDetonatorParameters(messages, 2);
return a.exec();
}
*/