MySQL Connector/C++ を用いた C++ アプリケーションからのデータベース接続は、パフォーマンスと信頼性を両立させるための鍵となる技術です。本稿では、Visual Studio 2019(および互換環境)を前提に、公式ドライバの現代的な使い方を再構成し、セキュアな接続管理、リソース自動解放、エラーハンドリングの強化、およびマルチスレッド対応を実現する手法を解説します。
環境準備と最小限の依存設定
MySQL Connector/C++ の最新安定版(v8.0+)は、CMake ベースのビルドが推奨されています。Windows 環境では、バイナリ配布版ではなく、mysql-connector-c++-8.0.x-winx64-vs142.zip(VS2019 互換)をダウンロードし、C:\mysqlcpp へ展開します。このパスを基準に、プロジェクトのプロパティを以下のように設定します:
- インクルードディレクトリ:
C:\mysqlcpp\include - ライブラリディレクトリ:
C:\mysqlcpp\lib\vs142 - 追加の依存ライブラリ:
mysqlcppconn8.lib(静的リンク用)またはmysqlcppconn8.dll(動的リンク時、実行時 PATH に含める)
RAII を活用した安全な接続管理
従来の裸のポインタによるリソース管理はメモリリークや二重解放のリスクがあります。以下は、std::unique_ptr とカスタムデリータを組み合わせた安全な実装例です:
#include <mysql_driver.h>
#include <mysql_connection.h>
#include <cppconn/prepared_statement.h>
#include <cppconn/resultset.h>
#include <memory>
#include <iostream>
// カスタムデリータ:sql::Connection の適切な破棄を保証
struct ConnectionDeleter {
void operator()(sql::Connection* ptr) const noexcept {
if (ptr) delete ptr;
}
};
using SafeConnection = std::unique_ptr<sql::Connection, ConnectionDeleter>;
SafeConnection establishSecureConnection() {
try {
auto driver = sql::mysql::get_mysql_driver_instance();
// SSL 暗号化を有効化(必須ではないが推奨)
auto conn = driver->connect(
"tcp://127.0.0.1:3306",
"app_user",
"secure_password"
);
conn->setSchema("inventory_db");
conn->setAutoCommit(false); // トランザクション制御の明示化
return SafeConnection{conn};
} catch (const sql::SQLException& e) {
std::cerr << "[FATAL] DB 接続失敗: " << e.what()
<< " (Code: " << e.getErrorCode() << ")\n";
throw;
}
}
int main() {
try {
auto db = establishSecureConnection();
auto stmt = db->prepareStatement("SELECT id, name FROM products WHERE price > ?");
stmt->setDouble(1, 100.0);
auto rs = stmt->executeQuery();
while (rs->next()) {
std::cout << "ID: " << rs->getInt("id")
<< ", 名前: " << rs->getString("name") << "\n";
}
db->commit(); // 明示的なコミット
} catch (const std::exception& ex) {
std::cerr << "アプリケーションエラー: " << ex.what() << "\n";
return 1;
}
return 0;
}
接続プールの導入とスレッドセーフな利用
高負荷アプリケーションでは、接続の生成・破棄オーバーヘッドを削減するために接続プールが不可欠です。Connector/C++ v8.0 以降では、sql::ConnectionPool クラスが提供されています。以下は、スレッドローカル接続を確保する実装です:
#include <thread>
#include <vector>
#include <mutex>
#include <shared_mutex>
class ThreadSafeConnectionPool {
private:
std::shared_ptr<sql::ConnectionPool> pool_;
mutable std::shared_mutex mutex_;
public:
ThreadSafeConnectionPool(const std::string& url,
const std::string& user,
const std::string& pass,
int min = 2, int max = 10)
: pool_(std::make_shared<sql::ConnectionPool>(
"com.mysql.cj.jdbc.Driver",
url, user, pass,
min, max, true)) {}
std::unique_ptr<sql::Connection> acquire() {
std::shared_lock<std::shared_mutex> lock(mutex_);
return std::unique_ptr<sql::Connection>(pool_->getConnection());
}
};
void workerTask(ThreadSafeConnectionPool& pool, int id) {
auto conn = pool.acquire();
// 各スレッドが独立した接続を使用 → 競合なし
std::cout << "スレッド " << id << " が接続を取得\n";
}
エラー分類と回復戦略
単一の catch では不十分です。ネットワーク障害(一時的)、認証失敗(永続的)、SQL 構文エラー(コード修正が必要)など、エラーの種類に応じた処理を実装します:
void handleDatabaseError(const sql::SQLException& e) {
const auto code = e.getErrorCode();
switch (code) {
case 1045: // Access denied
std::cerr << "[AUTH_ERROR] 認証情報が無効です。\n";
break;
case 2003: // Can't connect to MySQL server
std::cerr << "[NETWORK_ERROR] サーバーへの接続に失敗しました。再試行中...\n";
// 再試行ロジックをここに挿入
break;
case 1054: // Unknown column
std::cerr << "[SCHEMA_ERROR] クエリに存在しないカラムが指定されています。\n";
break;
default:
std::cerr << "[UNEXPECTED_ERROR] 予期せぬエラー: " << e.what() << "\n";
}
}
実行時ライブラリ配置のベストプラクティス
動的リンクを利用する場合、mysqlcppconn8.dll の配置先は以下の順序で検索されます:
- 実行ファイルと同じディレクトリ
- Windows システムディレクトリ(
%SystemRoot%\System32) - 環境変数
PATHに列挙されたディレクトリ
デプロイ時は、アプリケーションのバイナリディレクトリに .dll を同梱し、PATH への追加は避けることを推奨します。これは、他のアプリケーションとの衝突を防ぎ、バージョン管理を明確にするためです。