MFCとデータベース - CRUDの実行
概要
MFCにおけるデータベースアクセスは、CDatabase
クラスとCRecordset
クラスを中心に構築される。
これらのクラスはODBCを介して、様々なデータベースと通信を行う。
- CDatabaseクラス
- データベース接続を管理する。
- CRecordsetクラス
- クエリの実行結果や操作を扱う。
データベース接続を開始するには、CDatabaseクラスのインスタンスを生成して、OpenメソッドまたはOpenExメソッドを実行する。
接続文字列には、DSN、ユーザ名、パスワード等の認証情報を含めることができる。
データの取得ではCRecordsetクラスを通じて行う。
CRecordsetクラスは、特定のテーブルやクエリ結果に対応しており、レコードの追加、更新、削除等のCRUD操作が可能である。
また、フィールドバインディング機能を使用することにより、クラスのメンバ変数とデータベースのフィールドを直接マッピングすることができる。
トランザクション管理においては、CDatabaseクラスはBeginTransメソッド、Commitメソッド、Rollbackメソッドを提供しており、これらを使用することによりトランザクション制御が可能である。
複数の更新操作を1つの論理的な単位として扱う場合に有効である。
エラーハンドリングでは、データベース操作時のエラーはCDBException
クラスとして捕捉できる。
セキュリティ対策として、パラメータ化クエリを使用することにより、SQLインジェクション攻略のリスクを軽減することができる。
MFCでは、CCommand
クラスを使用してパラメータ化クエリを実装できる。
マルチスレッド環境でデータベースアクセスを行う場合は、適切な同期機構を実装する必要がある。
各スレッドで個別のデータベース接続を使用することが推奨される。
また、アプリケーションの要件に応じて、キャッシング機構の導入や接続プーリングの実装等も検討するとよい。
ODBCを使用したデータベースの更新手順は、以下に示す2つの項目からなる。
- ODBCアドミニストレータでのデータソース登録
- データベースの操作
ODBCアドミニストレータでのデータソース登録
データソースを登録することにより、アプリケーションからDSN名を使用してデータベースに接続することができる。
アプリケーションがx86向けの場合はx86向けのODBC、アプリケーションがAMD64の場合はAMD64向けのODBCを使用する。
※注意
必要なデータベースドライバが事前にインストールされている必要がある。
ネットワーク接続の場合、ファイアウォールのポート開放が必要となる。
ODBCデータソースアドミニストレーターの起動方法
- [コントロールパネル] - [Winodwsツール] または [管理ツール] - [ODBC データ ソース (64ビット)] または [ODBC データ ソース (32ビット)]を選択する。
- [Ctrl] - [R]キーを同時押下して、"ODBC"と入力・検索する。
- 64ビットアプリケーションの場合は、C:\Windows\SysWOW64\odbcad32.exeを実行する。
システムDSNの追加手順
- [システムDSN]タブを選択する。
- [追加]ボタンを押下する。
- 使用するデータベースドライバを選択する。
- SQL Serverの場合
- [SQL Server] または [ODBC Driver XX for SQL Server]
- Oracleの場合
- [Oracle in XXXXX]
- MySQLの場合
- [MySQL ODBC X.X Driver]
- SQL Serverの場合
- [完了]ボタンを選択する。
DSNの設定例
SQL Server用のDSN設定例
- DSNの設定を行う。
- 名前
- アプリケーションで使用するDSN名を入力 (例 : SQLServerTest)
- 説明
- 任意の説明を入力
- サーバー
- SQLServerのインスタンス名を選択
- 認証方法の選択
- Windows認証
- SQLServer認証 (ユーザIDとパスワードが必要)
- [デフォルトデータベースの変更]から、接続するデータベースを選択する。
- 名前
- [次へ]ボタンを押下する。
- 必要に応じて追加オプションを設定する。
- [完了]ボタンを押下する。
- [テストデータソース]で接続確認を行う。
Oracle用のDSN設定例
- DSNの設定を行う。
- [データソース名]
- DSN名を入力する。
- [説明]
- 任意の説明を入力する。
- [TNSサービス名]
- 接続するOracleサービスを選択する。
- [UserID]
- デフォルトのユーザID (オプション)
- [データソース名]
- [OK]ボタンを押下する。
MySQL用のDSN設定例
- DSNの設定を行う。
- [Data Source Name]
- DSN名を入力する。
- [TCP/IP Server]
- データベースサーバのホスト名またはIPアドレスを入力する。
- [Port]
- MySQLのポート番号を入力する。(デフォルト3306番)
- [User]
- データベースユーザ名を入力する。
- [Password]
- パスワードを入力する。
- [Database]
- 接続するデータベース名する。
- [Data Source Name]
- [Test]ボタンを押下して、接続テストを行う。
- [OK]ボタンを押下する。
エラー関連
ドライバが表示されない場合
適切なドライバをインストールする。
接続テストに失敗する場合
ネットワーク設定および認証情報を確認する。
データベースが表示されない場合
アクセス権限を確認する。
接続の確立
データベース接続を開始するには、CDatabaseクラスのインスタンスを生成して、OpenメソッドまたはOpenExメソッドを実行する。
接続文字列には、DSN、ユーザ名、パスワード等の認証情報を含めることができる。
// 接続文字列の制定
CString strConnect = _T("DSN=MyDataSource;UID=user;PWD=password;");
// データベースの接続
CDatabase database;
database.OpenEx(strConnect, CDatabase::noOdbcDialog);
エラーハンドリング
データベースの接続エラー、クエリ実行エラー、その他の予期せぬエラー等に対応することが重要である。
CString strConnect = _T("DSN=TestDB;");
CDatabase db;
CRecordset rs(&db);
try {
// データベース接続
if (!db.OpenEx(strConnect, CDatabase::noOdbcDialog)) {
AfxMessageBox(_T("データベース接続に失敗"));
return;
}
// クエリ実行
rs.Open(CRecordset::forwardOnly, _T("SELECT * FROM TestTable"));
// データ処理
while (!rs.IsEOF()) {
// データ処理コード
rs.MoveNext();
}
}
catch (CDBException *e) {
CString strError;
strError.Format(_T("データベースエラー: %s"), e->m_strError);
AfxMessageBox(strError);
e->Delete();
}
catch (...) {
AfxMessageBox(_T("予期せぬエラーが発生"));
}
レコードの抽出
#include <afxdb.h>
void CSampleDlg::OnBnClickedQueryButton1()
{
// 接続文字列生成
CString strCon = "DSN=MS Access 97 Database;UID=Admin;PWD=";
// Windows認証でSQLServerを使用する場合 => CString strCon = "DSN = SQLServerTest;";
// 接続
CDatabase db;
db.OpenEx( _T( strCon ), CDatabase::noOdbcDialog );
// db.OpenEx( _T( strCon ), CDatabase::openReadOnly | CDatabase::noOdbcDialog ); // 読み込み専用
// CDatabase::openReadOnly データソースを読み取り専用で開く
// CDatabase::noOdbcDialog 必要な情報が提供されているかに関わらず、ODBC接続のダイアログボックスを表示しない
// CDatabase::forceOdbcDialog ODBC接続のダイアログ ボックスを常に表示する
// 実行
CRecordset rs(&db);
try {
// SQLコマンドの実行
rs.Open( CRecordset::forwardOnly, _T("select * from test") );
CODBCFieldInfo fi;
short nFields = rs.GetODBCFieldCount();
//フィールド名を表示
if (!rs.IsEOF()) {
for(short index = 0; index < rs.GetODBCFieldCount(); index++) {
rs.GetODBCFieldInfo(index, fi);
TRACE("%s\n",fi.m_strName);
}
}
// 値を表示
while (!rs.IsEOF()) {
for(short index = 0; index < rs.GetODBCFieldCount(); index++) {
CString strValue;
rs.GetFieldValue(index, _T(strValue));
TRACE("%s\n", strValue);
}
rs.MoveNext(); // 次のレコードに移動
}
}
catch (...) {
// SQLがエラーを発生した場合はここで捕捉
// メモリリークのように見えるがODBCドライバが自動的に解放する
}
rs.Close();
db.Close();
}
レコードの挿入
基本的な挿入
#include <afxdb.h>
// データベース接続文字列の設定
// MS Access 97データベースに接続する場合の接続文字列
CString strCon = "DSN=MS Access 97 Database;UID=Admin;PWD=";
// 注意 : Windows認証でSQLServerを使用する場合は以下の接続文字列を使用
// CString strCon = "DSN = SQLServerTest;";
// データベースオブジェクトの作成と接続
CDatabase db;
db.OpenEx(_T(strCon), CDatabase::noOdbcDialog);
// データベース接続オプションの説明:
// CDatabase::noOdbcDialog - ODBCの接続ダイアログを表示せずに接続を試みる
// CDatabase::forceOdbcDialog - 常にODBCの接続ダイアログを表示する
// CDatabase::openReadOnly - 読み取り専用モードでデータベースを開く
try {
// トランザクションの開始
// データの整合性を保つため、INSERT操作はトランザクション内で実行することを推奨
db.BeginTrans();
// 日付文字列のフォーマット(YYYY-MM-DD HH:MM:SS)
COleDateTime currentDateTime = COleDateTime::GetCurrentTime();
CString strDateTime = currentDateTime.Format(_T("%Y-%m-%d %H:%M:%S"));
// 数値の金額をフォーマット (小数点以下2桁)
double dblAmount = 1234.56;
CString strAmount;
strAmount.Format(_T("%.2f"), dblAmount);
// バイナリデータの準備 (例 : 16進数データ)
BYTE binaryData[] = {0x01, 0x02, 0x03, 0x04};
CString strBinary;
for (int i = 0; i < sizeof(binaryData); i++) {
CString temp;
temp.Format(_T("%02X"), binaryData[i]);
strBinary += temp;
}
// INSERT文の実行
// 注意:実際のテーブル構造に合わせてカラム名と値を設定する必要があります
CString strSQL;
strSQL.Format(_T("INSERT INTO test (column1, column2, column3) VALUES ('%s', %d, '%s')"),
_T("サンプル値1"), // 文字列型カラムの例
123, // 数値型カラムの例
_T("サンプル値2")); // 文字列型カラムの例
// SQL文の実行
// ExecuteSQL関数を使用してINSERT文を実行
db.ExecuteSQL(strSQL);
// トランザクションのコミット
// エラーが発生しなかった場合、変更を確定
db.CommitTrans();
}
catch (CDBException *e) {
// データベースエラーの処理
// エラーメッセージの取得と表示
TRACE(_T("データベースエラー: %s\n"), e->m_strError);
// トランザクションのロールバック
// エラーが発生した場合、変更を取り消し
db.Rollback();
// 例外オブジェクトの解放
e->Delete();
}
catch (...) {
// その他の予期しないエラーの処理
TRACE(_T("予期しないエラーが発生しました\n"));
// トランザクションのロールバック
db.Rollback();
}
// データベース接続のクローズ
db.Close();
各データ型の挿入
- VARCHAR / NVARCHAR
- 文字列型 (NVARCHARはUnicode対応)
- 数値型 (INT, BIGINT, SMALLINT, TINYINT)
- 適切な範囲の値を使用する。
- 小数点数 (DECIMAL, FLOAT)
- 精度を考慮したフォーマット
- 日付時刻型 (DATE, DATETIME, DATETIME2, TIME)
- COleDateTimeクラスを使用して現在日時を取得する。
- 各日付時刻型に応じた適切なフォーマットを適用する。
- バイナリ型
- バイト配列を16進数文字列に変換して挿入する。
- 0xプレフィックスを付加して、SQLに組み込む。
- UNIQUEIDENTIFIER
- GUID形式の文字列
- Unicode対応
- NVARCHAR型には、N'文字列'の形式で値を指定する。
- 日本語等の全角文字も正しく扱うことが可能である。
// INSERT文の作成
CString strSQL;
strSQL.Format(_T("INSERT INTO SampleTable ")
_T("(VarcharColumn, ") // VARCHAR型
_T("NvarcharColumn, ") // NVARCHAR型
_T("IntColumn, ") // INT型
_T("BigIntColumn, ") // BIGINT型
_T("SmallIntColumn, ") // SMALLINT型
_T("TinyIntColumn, ") // TINYINT型
_T("BitColumn, ") // BIT型
_T("DecimalColumn, ") // DECIMAL型
_T("FloatColumn, ") // FLOAT型
_T("DateColumn, ") // DATE型
_T("DateTimeColumn, ") // DATETIME型
_T("DateTime2Column, ") // DATETIME2型
_T("TimeColumn, ") // TIME型
_T("BinaryColumn, ") // BINARY型
_T("UniqueIdentifier) ") // UNIQUEIDENTIFIER型
_T("VALUES ")
_T("('%s', ") // VARCHAR
_T("N'%s', ") // NVARCHAR(Unicode文字列)
_T("%d, ") // INT
_T("%I64d, ") // BIGINT
_T("%d, ") // SMALLINT
_T("%d, ") // TINYINT
_T("%d, ") // BIT
_T("%s, ") // DECIMAL
_T("%f, ") // FLOAT
_T("'%s', ") // DATE
_T("'%s', ") // DATETIME
_T("'%s', ") // DATETIME2
_T("'%s', ") // TIME
_T("0x%s, ") // BINARY
_T("'%s')"), // UNIQUEIDENTIFIER
// 各データ型の値を指定
_T("テスト文字列"), // VARCHAR値
_T("Unicode文字列"), // NVARCHAR値
12345, // INT値
9223372036854775807LL, // BIGINT値 (最大値)
32767, // SMALLINT値
255, // TINYINT値
1, // BIT値 (0または1)
strAmount, // DECIMAL値
123.456789, // FLOAT値
currentDateTime.Format(_T("%Y-%m-%d")), // DATE値
strDateTime, // DATETIME値
strDateTime, // DATETIME2値
currentDateTime.Format(_T("%H:%M:%S.0000000")), // TIME値
strBinary, // BINARY値
_T("12345678-1234-1234-1234-123456789012") // UNIQUEIDENTIFIER値
);
NULL値を含む挿入
NULL値を許可するカラムの場合、明示的にNULLが指定できる。
NULL値を含む専用のINSERT文を追加する。
CString strSQLWithNull;
strSQLWithNull.Format(_T("INSERT INTO SampleTable ")
_T("(VarcharColumn, IntColumn) ")
_T("VALUES ")
_T("(NULL, NULL)"));
トランザクション
ロールバック処理自体も失敗する可能性があるため、2重のtry-catch構造を使用することにより、ロールバック処理中のエラーも適切に処理できる。
// 接続文字列の生成
CString strCon = _T("DSN=MS Access 97 Database;UID=Admin;PWD=");
CDatabase db;
bool bTransStarted = false; // トランザクションの状態を追跡するフラグ
try {
// 接続
if (!db.OpenEx(strCon, CDatabase::noOdbcDialog)) {
AfxMessageBox(_T("データベース接続に失敗しました"));
return -1;
}
// トランザクション開始
db.BeginTrans();
bTransStarted = true;
// SQLコマンドの実行
db.ExecuteSQL(_T("create table test(a text, b text)"));
// db.ExecuteSQL(_T("insert into test(a,b) values('inaba','minoru')"));
// db.ExecuteSQL(_T("delete from test where a='inaba'"));
// db.ExecuteSQL(_T("DROP TABLE test"));
// トランザクションのコミット
db.CommitTrans();
bTransStarted = false;
}
catch (CDBException *e) {
// データベース固有のエラー処理
if (bTransStarted) {
try {
db.Rollback();
}
catch (...) {
AfxMessageBox(_T("ロールバック中にエラーが発生しました"));
}
}
CString strError;
strError.Format(_T("データベースエラー: %s"), e->m_strError);
AfxMessageBox(strError);
e->Delete();
}
catch (...) {
// その他の予期せぬエラー処理
if (bTransStarted) {
try {
db.Rollback();
}
catch (...) {
AfxMessageBox(_T("ロールバック中にエラーが発生しました"));
}
}
AfxMessageBox(_T("予期せぬエラーが発生しました"));
}
// データベースを閉じる
if (db.IsOpen()) db.Close();
注意点
SQL Server等でSELECT文を発行すると、datetime型カラム等が表示できない場合がある。
そのため、GetFieldValueメソッドの引数にCDBVariantを使用する。
メンバ変数名 | データ型 |
---|---|
m_boolVal | BOOL |
m_chVal | unsigned char |
m_dblVal | double |
m_dwType | DWORD(現在格納されている値のデータ型) |
m_fltVal | float |
m_iVal | short |
m_lVal | long |
m_pbinary | CLongBinaryオブジェクトへのポインタ |
m_pdate | TIMESTAMP_STRUCTオブジェクトへのポインタ |
m_pstring | CStringオブジェクトへのポインタ |
上表のCDBVariant::m_dwTypeでは、CDBVariantオブジェクトの共用体データメンバに現在格納されている値のデータ型が入る。
共用体にアクセスする前に、共用体のどのメンバにアクセスするかを決めるために、m_dwTypeの値をチェックする必要がある。
下表は、m_dwTypeの値と対応する共用体メンバの一覧である。
共用体データ型 | 共用体データメンバ |
---|---|
DBVT_NULL | 有効な共用体メンバはなく、アクセスできません。 |
DBVT_BOOL | m_boolVal |
DBVT_UCHAR | m_chVal |
DBVT_SHORT | m_iVal |
DBVT_LONG | m_lVal |
DBVT_SINGLE | m_fltVal |
DBVT_DOUBLE | m_dblVal |
DBVT_DATE | m_pdate |
DBVT_STRING | m_pstring |
DBVT_BINARY | m_pbinary |
CString strCon = "DSN = SQLServerTest;";
CDatabase db;
db.OpenEx(_T( strCon ), CDatabase::noOdbcDialog);
CRecordset rs( &db );
try
{
rs.Open( CRecordset::forwardOnly, _T("select * from test") ); // SQLコマンドの実行
// 値を表示
while (!rs.IsEOF()) {
for(short index = 0; index < rs.GetODBCFieldCount(); index++) {
CDBVariant val;
rs.GetFieldValue(index, val);
switch(val.m_dwType) {
case DBVT_NULL :
TRACE("NULL 無効の値\n");
break;
case DBVT_BOOL :
TRACE("m_boolVal BOOL 型\n");
TRACE("%d\n",val.m_boolVal);
break;
case DBVT_UCHAR :
TRACE("m_chVal unsigned char 型\n");
TRACE("%s\n",val.m_chVal);
break;
case DBVT_SHORT :
TRACE("m_iVal short 型\n");
TRACE("%d\n",val.m_iVal);
break;
case DBVT_LONG :
TRACE("m_lVal long 型\n");
TRACE("%d\n",val.m_lVal);
break;
case DBVT_SINGLE :
TRACE("m_fltVal float 型\n");
TRACE("%f\n",val.m_fltVal);
break;
case DBVT_DOUBLE :
TRACE("m_dblVal double 型\n");
TRACE("%f\n",val.m_dblVal);
break;
case DBVT_DATE :
TRACE("m_pdate TIMESTAMP_STRUCT 型のオブジェクトへのポインタ\n");
// NULLチェック
if (val.m_pdate != nullptr) {
TIMESTAMP_STRUCT *pT = val.m_pdate;
CString strDateTime;
strDateTime.Format(_T("%04d/%02d/%02d %02d:%02d:%02d"), pT->year, pT->month, pT->day, pT->hour, pT->minute, pT->second);
TRACE(_T("Field: %s, Value: %s\n"), fieldName, strDateTime);
}
break;
case DBVT_STRING :
if (val.m_pstring != nullptr) {
TRACE("m_pstring CString 型のオブジェクトへのポインタ\n");
TRACE(_T("Field: %s, Value: %s\n"), fieldName, *val.m_pstring);
}
break;
case DBVT_BINARY :
TRACE("m_pbinary\n CLongBinary 型のオブジェクトへのポインタ");
break;
default :
break;
}
}
rs.MoveNext();
}
}
catch (CDBException *e) {
TRACE(_T("Database error: %s\n"), e->m_strError);
e->Delete();
}
catch (...) {
TRACE(_T("不明なデータベースエラーが発生\n"));
}
rs.Close();
db.Close();