「Qtの基礎 - データベース」の版間の差分
ナビゲーションに移動
検索に移動
22行目: | 22行目: | ||
<br> | <br> | ||
<u>また、使用時にはエラーハンドリングおよびセキュリティ対策 (SQLインジェクション対策等) を適切に実装することを推奨する。</u><br> | <u>また、使用時にはエラーハンドリングおよびセキュリティ対策 (SQLインジェクション対策等) を適切に実装することを推奨する。</u><br> | ||
* ロギング機能の追加 | |||
* リトライメカニズムの実装 (一時的なネットワーク問題などに対処するため) | |||
* 詳細なエラータイプの定義 (例:接続エラー、クエリエラー等) | |||
* パラメータのバリデーション | |||
* SQLインジェクション対策の強化 | |||
<br> | |||
開発向けMariaDBライブラリをインストールする。<br> | |||
# RHEL | |||
sudo dnf install mariadb-devel | |||
# SUSE | |||
sudo zypper install libmariadbd-devel | |||
<br> | <br> | ||
<syntaxhighlight lang="cmake"> | <syntaxhighlight lang="cmake"> | ||
103行目: | 115行目: | ||
bool deleteRecord(int id); | bool deleteRecord(int id); | ||
QList<QMap<QString, QVariant>> readAllRecords(); | QList<QMap<QString, QVariant>> readAllRecords(); | ||
void beginTransaction(); | |||
void commitTransaction(); | |||
void rollbackTransaction(); | |||
// 複数の操作をトランザクションで実行する場合のメソッド | |||
void performComplexOperation(const QString &name1, int age1, const QString &name2, int age2); | |||
private: | private: | ||
162行目: | 181行目: | ||
QString errorMessage = QString("%1 failed: %2").arg(operation, error.text()); | QString errorMessage = QString("%1 failed: %2").arg(operation, error.text()); | ||
throw DatabaseException(errorMessage); | throw DatabaseException(errorMessage); | ||
} | |||
// トランザクションを開始 | |||
void DatabaseManager::beginTransaction() | |||
{ | |||
checkConnection(); | |||
if (!db.transaction()) { | |||
throw DatabaseException("Failed to start transaction: " + db.lastError().text()); | |||
} | |||
qDebug() << "Transaction started"; | |||
} | |||
// トランザクションをコミット | |||
void DatabaseManager::commitTransaction() | |||
{ | |||
checkConnection(); | |||
if (!db.commit()) { | |||
throw DatabaseException("Failed to commit transaction: " + db.lastError().text()); | |||
} | |||
qDebug() << "Transaction committed"; | |||
} | |||
// トランザクションをロールバック | |||
void DatabaseManager::rollbackTransaction() | |||
{ | |||
checkConnection(); | |||
if (!db.rollback()) { | |||
throw DatabaseException("Failed to rollback transaction: " + db.lastError().text()); | |||
} | |||
qDebug() << "Transaction rolled back"; | |||
} | |||
// 複数の操作を1つのトランザクションとして実行するメソッド | |||
void DatabaseManager::performComplexOperation(const QString &name1, int age1, const QString &name2, int age2) | |||
{ | |||
try { | |||
beginTransaction(); | |||
insertRecord(name1, age1); | |||
insertRecord(name2, age2); | |||
commitTransaction(); | |||
qDebug() << "Complex operation completed successfully"; | |||
} | |||
catch (const DatabaseException& e) { | |||
rollbackTransaction(); | |||
throw DatabaseException("Complex operation failed: " + QString(e.what())); | |||
} | |||
} | } | ||
274行目: | 348行目: | ||
dbManager.createTable(); | dbManager.createTable(); | ||
// | // トランザクションを使用した複雑な操作 | ||
dbManager. | dbManager.performComplexOperation("Alice", 28, "Bob", 32); | ||
dbManager. | |||
// 個別のトランザクション管理の例 | |||
dbManager.beginTransaction(); | |||
try { | |||
// 既存のレコードの更新 | |||
dbManager.updateRecord(1, "John Doe Updated", 31); | |||
// 任意のレコードの削除 | |||
dbManager.deleteRecord(2); | |||
dbManager.commitTransaction(); | |||
} | |||
catch (const DatabaseException& e) { | |||
dbManager.rollbackTransaction(); | |||
qCritical() << "Transaction failed:" << e.what(); | |||
} | |||
// 全てのレコードを読み込む | // 全てのレコードを読み込む | ||
QList<QMap<QString, QVariant>> records = dbManager.readAllRecords(); | QList<QMap<QString, QVariant>> records = dbManager.readAllRecords(); | ||
for (const auto& record : records) { | for (const auto &record : records) { | ||
qDebug() << "ID:" << record["id"].toInt() | |||
<< "Name:" << record["name"].toString() | |||
<< "Age:" << record["age"].toInt(); | |||
} | |||
// 全てのレコードを読み込む | // 全てのレコードを読み込む | ||
records = dbManager.readAllRecords(); | records = dbManager.readAllRecords(); | ||
312行目: | 394行目: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br> | |||
トランザクションを使用するメリットを以下に示す。<br> | |||
* データの一貫性 | |||
*: 複数の操作を1つの単位として扱うことができる。 | |||
* エラー回復 | |||
*: 問題が発生した場合、簡単に全ての変更を元に戻すことができる。 | |||
* パフォーマンス | |||
*: 多数の操作を一度のトランザクションで行うことにより、データベースとの通信回数を減らし、パフォーマンスを向上させることができる。 | |||
<br> | |||
ただし、トランザクション管理を使用する場合は、以下に示す事柄に注意すること。<br> | |||
* トランザクションは必要な場合にのみ使用して、不必要に長く保持しないようにする。 | |||
* ネストされたトランザクションの扱いには注意が必要である。<br><code>QSQLDatabase</code>クラスは、一般的にネストされたトランザクションをサポートしていない。 | |||
* 大規模なデータ操作を行う場合は、適切なチェックポイントを設定することを検討する。 | |||
<br><br> | <br><br> | ||
2024年8月14日 (水) 08:36時点における版
概要
MariaDB
以下の例では、MariaDBのデータベースを使用して、以下に示すようなテーブルを作成して基本的なCRUD操作を行っている。
実務では、必要に応じてフィールドを追加、または、データ型や制約を調整したりする必要がある。
- テーブル名
- people
- id
- INT型
- 制約 1 : AUTO_INCREMENT
- 制約 2 : PRIMARY KEY
- 各レコードを一意に識別するための自動増分の主キー。
- name
- VARCHAR(40)型
- 制約 : NOT NULL
- 名前を格納するフィールドで、最大40文字まで保存可能。
- age
- INT型
- 年齢を格納する整数型のフィールド。
また、使用時にはエラーハンドリングおよびセキュリティ対策 (SQLインジェクション対策等) を適切に実装することを推奨する。
- ロギング機能の追加
- リトライメカニズムの実装 (一時的なネットワーク問題などに対処するため)
- 詳細なエラータイプの定義 (例:接続エラー、クエリエラー等)
- パラメータのバリデーション
- SQLインジェクション対策の強化
開発向けMariaDBライブラリをインストールする。
# RHEL sudo dnf install mariadb-devel # SUSE sudo zypper install libmariadbd-devel
# CMakeLists.txtファイル
cmake_minimum_required(VERSION 3.21)
project(QtMariaDBProject LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Sql)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Sql)
# MariaDB Connector/Cを検索
find_path(MARIADB_INCLUDE_DIR mysql.h
/usr/include/mysql
)
find_library(MARIADB_LIBRARY NAMES mariadb
PATHS
/usr/lib
/usr/lib64
)
if(NOT MARIADB_INCLUDE_DIR OR NOT MARIADB_LIBRARY)
message(FATAL_ERROR "MariaDB Connector/C not found!")
endif()
add_executable(QtMariaDBProject
main.cpp
DataBaseManager.h
DataBaseManager.cpp
)
# QtライブラリとMariaDBライブラリをリンク
target_link_libraries(QtMariaDBProject PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Sql
${MARIADB_LIBRARY}
)
# MariaDBのヘッダファイルのディレクトリを追加
target_include_directories(QtMariaDBProject PRIVATE
${MARIADB_INCLUDE_DIR}
)
// DataBaseManager.hファイル
#include <QtSql>
#include <QString>
#include <QSqlError>
#include <stdexcept>
#include <QDebug>
class DatabaseException : public std::runtime_error
{
public:
DatabaseException(const QString& message) : std::runtime_error(message.toStdString()) {}
};
class DatabaseManager
{
public:
DatabaseManager();
~DatabaseManager();
bool openConnection();
void closeConnection();
bool createTable();
bool insertRecord(const QString &name, int age);
bool updateRecord(int id, const QString &name, int age);
bool deleteRecord(int id);
QList<QMap<QString, QVariant>> readAllRecords();
void beginTransaction();
void commitTransaction();
void rollbackTransaction();
// 複数の操作をトランザクションで実行する場合のメソッド
void performComplexOperation(const QString &name1, int age1, const QString &name2, int age2);
private:
QSqlDatabase db;
void checkConnection();
void handleDatabaseError(const QString &operation, const QSqlError &error);
};
// DataBaseManager.cppファイル
#include "DataBaseManager.h"
DatabaseManager::DatabaseManager()
{
db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("<ホスト名またはIPアドレス 例: localhost>");
db.setDatabaseName("<データベース名 例: sampledb>");
db.setUserName("<データベースのユーザ名>");
db.setPassword("<データベースのユーザ名のパスワード>");
}
DatabaseManager::~DatabaseManager()
{
closeConnection();
}
// データベースの接続
bool DatabaseManager::openConnection()
{
if (!db.open()) {
throw DatabaseException("Failed to open database connection: " + db.lastError().text());
}
qDebug() << "Database connection opened successfully";
}
// データベースの切断
void DatabaseManager::closeConnection()
{
if (db.isOpen()) {
db.close();
qDebug() << "Database connection closed";
}
}
// 接続の確認
void DatabaseManager::checkConnection()
{
if (!db.isOpen()) {
throw DatabaseException("Database connection is not open");
}
}
// エラーハンドリング処理
void DatabaseManager::handleDatabaseError(const QString &operation, const QSqlError &error)
{
QString errorMessage = QString("%1 failed: %2").arg(operation, error.text());
throw DatabaseException(errorMessage);
}
// トランザクションを開始
void DatabaseManager::beginTransaction()
{
checkConnection();
if (!db.transaction()) {
throw DatabaseException("Failed to start transaction: " + db.lastError().text());
}
qDebug() << "Transaction started";
}
// トランザクションをコミット
void DatabaseManager::commitTransaction()
{
checkConnection();
if (!db.commit()) {
throw DatabaseException("Failed to commit transaction: " + db.lastError().text());
}
qDebug() << "Transaction committed";
}
// トランザクションをロールバック
void DatabaseManager::rollbackTransaction()
{
checkConnection();
if (!db.rollback()) {
throw DatabaseException("Failed to rollback transaction: " + db.lastError().text());
}
qDebug() << "Transaction rolled back";
}
// 複数の操作を1つのトランザクションとして実行するメソッド
void DatabaseManager::performComplexOperation(const QString &name1, int age1, const QString &name2, int age2)
{
try {
beginTransaction();
insertRecord(name1, age1);
insertRecord(name2, age2);
commitTransaction();
qDebug() << "Complex operation completed successfully";
}
catch (const DatabaseException& e) {
rollbackTransaction();
throw DatabaseException("Complex operation failed: " + QString(e.what()));
}
}
// テーブルの作成
bool DatabaseManager::createTable()
{
checkConnection();
QSqlQuery query;
if (!query.exec("CREATE TABLE IF NOT EXISTS people (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(40) NOT NULL, age INT)")) {
handleDatabaseError("Create table", query.lastError());
}
qDebug() << "Table 'people' created or already exists";
}
// 単一のレコードの追加
bool DatabaseManager::insertRecord(const QString& name, int age)
{
checkConnection();
QSqlQuery query;
query.prepare("INSERT INTO people (name, age) VALUES (:name, :age)");
query.bindValue(":name", name);
query.bindValue(":age", age);
if (!query.exec()) {
handleDatabaseError("Insert record", query.lastError());
}
qDebug() << "Record inserted successfully";
}
// 単一のレコードの更新
bool DatabaseManager::updateRecord(int id, const QString& name, int age)
{
checkConnection();
QSqlQuery query;
query.prepare("UPDATE people SET name = :name, age = :age WHERE id = :id");
query.bindValue(":id", id);
query.bindValue(":name", name);
query.bindValue(":age", age);
if (!query.exec()) {
handleDatabaseError("Update record", query.lastError());
}
if (query.numRowsAffected() == 0) {
throw DatabaseException(QString("No record found with id: %1").arg(id));
}
qDebug() << "Record updated successfully";
}
// レコードの削除
bool DatabaseManager::deleteRecord(int id)
{
checkConnection();
QSqlQuery query;
query.prepare("DELETE FROM people WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
handleDatabaseError("Delete record", query.lastError());
}
if (query.numRowsAffected() == 0) {
throw DatabaseException(QString("No record found with id: %1").arg(id));
}
qDebug() << "Record deleted successfully";
}
// 全てのレコードを読み込む
QList<QMap<QString, QVariant>> DatabaseManager::readAllRecords()
{
checkConnection();
QSqlQuery query("SELECT * FROM people");
QList<QMap<QString, QVariant>> results;
if (!query.exec()) {
handleDatabaseError("Read all records", query.lastError());
}
while (query.next()) {
QMap<QString, QVariant> record;
record["id"] = query.value(0);
record["name"] = query.value(1);
record["age"] = query.value(2);
results.append(record);
}
return results;
}
// main.cppファイル
int main()
{
DatabaseManager dbManager;
try {
// データーベースの接続
dbManager.openConnection();
// テーブルの作成
dbManager.createTable();
// トランザクションを使用した複雑な操作
dbManager.performComplexOperation("Alice", 28, "Bob", 32);
// 個別のトランザクション管理の例
dbManager.beginTransaction();
try {
// 既存のレコードの更新
dbManager.updateRecord(1, "John Doe Updated", 31);
// 任意のレコードの削除
dbManager.deleteRecord(2);
dbManager.commitTransaction();
}
catch (const DatabaseException& e) {
dbManager.rollbackTransaction();
qCritical() << "Transaction failed:" << e.what();
}
// 全てのレコードを読み込む
QList<QMap<QString, QVariant>> records = dbManager.readAllRecords();
for (const auto &record : records) {
qDebug() << "ID:" << record["id"].toInt()
<< "Name:" << record["name"].toString()
<< "Age:" << record["age"].toInt();
}
// 全てのレコードを読み込む
records = dbManager.readAllRecords();
for (const auto& record : records) {
qDebug() << "ID:" << record["id"].toInt()
<< "Name:" << record["name"].toString()
<< "Age:" << record["age"].toInt();
}
}
catch (const DatabaseException &e) {
// データベース関連のエラー
qCritical() << "Database error: " << e.what();
}
catch (const std::exception &e) {
// その他のエラー
qCritical() << "Unexpected error: " << e.what();
}
return 0;
}
トランザクションを使用するメリットを以下に示す。
- データの一貫性
- 複数の操作を1つの単位として扱うことができる。
- エラー回復
- 問題が発生した場合、簡単に全ての変更を元に戻すことができる。
- パフォーマンス
- 多数の操作を一度のトランザクションで行うことにより、データベースとの通信回数を減らし、パフォーマンスを向上させることができる。
ただし、トランザクション管理を使用する場合は、以下に示す事柄に注意すること。
- トランザクションは必要な場合にのみ使用して、不必要に長く保持しないようにする。
- ネストされたトランザクションの扱いには注意が必要である。
QSQLDatabase
クラスは、一般的にネストされたトランザクションをサポートしていない。 - 大規模なデータ操作を行う場合は、適切なチェックポイントを設定することを検討する。
SQLite3
Qtを使用してSQLite3のテーブルにアクセスする場合、まず、QSqlDatabase
クラスを使用してデータベースに接続する。
#include <QSqlDatabase>
#include <QSqlQuery>
// SQLiteデータベースに接続する
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("<SQLiteデータベース名>.sqlite"); // データベースファイル名
if (!db.open())
{
qDebug() << "データベースに接続できませんでした。";
return;
}
次に、QSqlQuery
クラスを使用してクエリを実行する。
以下の例では、テーブルのデータを取得している。
QSqlQuery query;
query.prepare("SELECT * FROM <SQLiteデータベースのテーブル名>");
if (query.exec())
{
while (query.next())
{ // レコードの処理
QString column1 = query.value(0).toString(); // 1番目の列
QString column2 = query.value(1).toString(); // 2番目の列
// 他の列も同様に取得できる
}
}
else
{
qDebug() << "クエリの実行に失敗しました.";
}
// データベース接続を閉じる
db.close();
SQLインジェクション対策
SQLインジェクション対策では、プレースホルダを使用してクエリを構築することが重要である。
以下の例では、":username"がプレースホルダとなり、ユーザからの入力等が直接クエリに組み込まれることなく、安全にクエリが実行される。
また、ユーザからの入力をクエリに組み込む場合、入力データを適切にサニタイズしてクエリに含めるようにすることにより、悪意のあるSQLコードが挿入されても実行されないようにする。
QSqlQuery query;
QString username = "user_input"; // ユーザからの入力等
// プレースホルダを使用して安全にクエリを構築
query.prepare("SELECT * FROM users WHERE username = :username");
query.bindValue(":username", username);
if (query.exec())
{
while (query.next())
{ // レコードの処理
// ...略
}
}
else
{
qDebug() << "クエリの実行に失敗しました.";
}