MFCとデータベース - CRUDの実行

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
2025年1月12日 (日) 05:06時点におけるWiki (トーク | 投稿記録)による版 (→‎SELECT)
ナビゲーションに移動 検索に移動

概要

MFCにおけるデータベースアクセスは、CDatabaseクラスとCRecordsetクラスを中心に構築される。
これらのクラスはODBCを介して、様々なデータベースと通信を行う。

  • CDatabaseクラス
    データベース接続を管理する。
  • CRecordsetクラス
    クエリの実行結果や操作を扱う。


データベース接続を開始するには、CDatabaseクラスのインスタンスを生成して、OpenメソッドまたはOpenExメソッドを実行する。
接続文字列には、DSN、ユーザ名、パスワード等の認証情報を含めることができる。

データの取得ではCRecordsetクラスを通じて行う。 CRecordsetクラスは、特定のテーブルやクエリ結果に対応しており、レコードの追加、更新、削除等のCRUD操作が可能である。
また、フィールドバインディング機能を使用することにより、クラスのメンバ変数とデータベースのフィールドを直接マッピングすることができる。

トランザクション管理においては、CDatabaseクラスはBeginTransメソッド、Commitメソッド、Rollbackメソッドを提供しており、これらを使用することによりトランザクション制御が可能である。
複数の更新操作を1つの論理的な単位として扱う場合に有効である。

エラーハンドリングでは、データベース操作時のエラーはCDBExceptionクラスとして捕捉できる。

セキュリティ対策として、パラメータ化クエリを使用することにより、SQLインジェクション攻略のリスクを軽減することができる。
MFCでは、CCommandクラスを使用してパラメータ化クエリを実装できる。

マルチスレッド環境でデータベースアクセスを行う場合は、適切な同期機構を実装する必要がある。
各スレッドで個別のデータベース接続を使用することが推奨される。

また、アプリケーションの要件に応じて、キャッシング機構の導入や接続プーリングの実装等も検討するとよい。

ODBCを使用したデータベースの更新手順は、以下に示す2つの項目からなる。

  1. ODBCアドミニストレータでのデータソース登録
  2. データベースの操作



ODBCアドミニストレータでのデータソース登録

データソースを登録することにより、アプリケーションからDSN名を使用してデータベースに接続することができる。

アプリケーションがx86向けの場合はx86向けのODBC、アプリケーションがAMD64の場合はAMD64向けのODBCを使用する。

※注意
必要なデータベースドライバが事前にインストールされている必要がある。
ネットワーク接続の場合、ファイアウォールのポート開放が必要となる。

ODBCデータソースアドミニストレーターの起動方法

  • [コントロールパネル] - [Winodwsツール] または [管理ツール] - [ODBC データ ソース (64ビット)] または [ODBC データ ソース (32ビット)]を選択する。
  • [Ctrl] - [R]キーを同時押下して、"ODBC"と入力・検索する。
  • 64ビットアプリケーションの場合は、C:\Windows\SysWOW64\odbcad32.exeを実行する。


システムDSNの追加手順

  1. [システムDSN]タブを選択する。
  2. [追加]ボタンを押下する。
  3. 使用するデータベースドライバを選択する。
    • SQL Serverの場合
      [SQL Server] または [ODBC Driver XX for SQL Server]
    • Oracleの場合
      [Oracle in XXXXX]
    • MySQLの場合
      [MySQL ODBC X.X Driver]
  4. [完了]ボタンを選択する。


DSNの設定例

SQL Server用のDSN設定例
  1. DSNの設定を行う。
    • 名前
      アプリケーションで使用するDSN名を入力 (例 : SQLServerTest)
    • 説明
      任意の説明を入力
    • サーバー
      SQLServerのインスタンス名を選択
    • 認証方法の選択
      Windows認証
      SQLServer認証 (ユーザIDとパスワードが必要)
    • [デフォルトデータベースの変更]から、接続するデータベースを選択する。
  2. [次へ]ボタンを押下する。
  3. 必要に応じて追加オプションを設定する。
  4. [完了]ボタンを押下する。
  5. [テストデータソース]で接続確認を行う。


Oracle用のDSN設定例
  1. DSNの設定を行う。
    • [データソース名]
      DSN名を入力する。
    • [説明]
      任意の説明を入力する。
    • [TNSサービス名]
      接続するOracleサービスを選択する。
    • [UserID]
      デフォルトのユーザID (オプション)
  2. [OK]ボタンを押下する。


MySQL用のDSN設定例
  1. DSNの設定を行う。
    • [Data Source Name]
      DSN名を入力する。
    • [TCP/IP Server]
      データベースサーバのホスト名またはIPアドレスを入力する。
    • [Port]
      MySQLのポート番号を入力する。(デフォルト3306番)
    • [User]
      データベースユーザ名を入力する。
    • [Password]
      パスワードを入力する。
    • [Database]
      接続するデータベース名する。
  2. [Test]ボタンを押下して、接続テストを行う。
  3. [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();
 }



トランザクション

ロールバック処理自体も失敗する可能性があるため、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を使用する。

表. 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の値と対応する共用体メンバの一覧である。

表. 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();