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

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

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();
 }



レコードの挿入

基本的な挿入

 #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を使用する。

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