「MFCとデータベース - CRUDの実行」の版間の差分

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動
(Wiki がページ「ODBCでデータベースを更新する」を「ODBCでデータベースを更新する(MFC)」に、リダイレクトを残さずに移動しました)
 
(同じ利用者による、間の16版が非表示)
1行目: 1行目:
== 概要 ==
== 概要 ==
ここでは、MFCとミドルウェアであるODBCを使用したデータベースの更新処理を記載する。<br>
MFCにおけるデータベースアクセスは、<code>CDatabase</code>クラスと<code>CRecordset</code>クラスを中心に構築される。<br>
上記を使用したデータベースの更新手順は、以下の2つの項目からなる。<br>
これらのクラスはODBCを介して、様々なデータベースと通信を行う。<br>
# '''ODBCアドミニストレータでのデータソース登録'''
<br>
# '''データベースの操作'''
* CDatabaseクラス
*: データベース接続を管理する。
* CRecordsetクラス
*: クエリの実行結果や操作を扱う。
<br>
データベース接続を開始するには、CDatabaseクラスのインスタンスを生成して、OpenメソッドまたはOpenExメソッドを実行する。<br>
接続文字列には、DSN、ユーザ名、パスワード等の認証情報を含めることができる。<br>
<br>
データの取得ではCRecordsetクラスを通じて行う。
CRecordsetクラスは、特定のテーブルやクエリ結果に対応しており、レコードの追加、更新、削除等のCRUD操作が可能である。<br>
また、フィールドバインディング機能を使用することにより、クラスのメンバ変数とデータベースのフィールドを直接マッピングすることができる。<br>
<br>
トランザクション管理においては、CDatabaseクラスはBeginTransメソッド、Commitメソッド、Rollbackメソッドを提供しており、これらを使用することによりトランザクション制御が可能である。<br>
複数の更新操作を1つの論理的な単位として扱う場合に有効である。<br>
<br>
エラーハンドリングでは、データベース操作時のエラーは<code>CDBException</code>クラスとして捕捉できる。<br>
<br>
セキュリティ対策として、パラメータ化クエリを使用することにより、SQLインジェクション攻略のリスクを軽減することができる。<br>
MFCでは、<code>CCommand</code>クラスを使用してパラメータ化クエリを実装できる。<br>
<br>
マルチスレッド環境でデータベースアクセスを行う場合は、適切な同期機構を実装する必要がある。<br>
各スレッドで個別のデータベース接続を使用することが推奨される。<br>
<br>
また、アプリケーションの要件に応じて、キャッシング機構の導入や接続プーリングの実装等も検討するとよい。<br>
<br>
ODBCを使用したデータベースの更新手順は、以下に示す2つの項目からなる。<br>
# ODBCアドミニストレータでのデータソース登録
# データベースの操作
<br><br>
<br><br>


== サンプルコード ==
== ODBCアドミニストレータでのデータソース登録 ==
<big>'''SELECT文を発行する'''</big>
データソースを登録することにより、アプリケーションからDSN名を使用してデータベースに接続することができる。<br>
  <source lang="cpp">
<br>
アプリケーションがx86向けの場合はx86向けのODBC、アプリケーションがAMD64の場合はAMD64向けのODBCを使用する。<br>
<br>
<u>※注意</u><br>
<u>必要なデータベースドライバが事前にインストールされている必要がある。</u><br>
<u>ネットワーク接続の場合、ファイアウォールのポート開放が必要となる。</u><br>
<br>
==== ODBCデータソースアドミニストレーターの起動方法 ====
* [コントロールパネル] - [Winodwsツール] または [管理ツール] - [ODBC データ ソース (64ビット)] または [ODBC データ ソース (32ビット)]を選択する。
* [Ctrl] - [R]キーを同時押下して、<u>"ODBC"</u>と入力・検索する。
* 64ビットアプリケーションの場合は、C:\Windows\SysWOW64\odbcad32.exeを実行する。
<br>
 
==== システムDSNの追加手順 ====
# [システムDSN]タブを選択する。
# [追加]ボタンを押下する。
# 使用するデータベースドライバを選択する。
#* SQL Serverの場合
#*: [SQL Server] または [ODBC Driver XX for SQL Server]
#* Oracleの場合
#*: [Oracle in XXXXX]
#*  MySQLの場合
#*: [MySQL ODBC X.X Driver]
# [完了]ボタンを選択する。
<br>
==== DSNの設定例 ====
===== SQL Server用のDSN設定例 =====
# DSNの設定を行う。
#* 名前
#*: アプリケーションで使用するDSN名を入力 (例 : SQLServerTest)
#* 説明
#*: 任意の説明を入力
#* サーバー
#*: SQLServerのインスタンス名を選択
#* 認証方法の選択
#*: Windows認証
#*: SQLServer認証 (ユーザIDとパスワードが必要)
#* [デフォルトデータベースの変更]から、接続するデータベースを選択する。
# [次へ]ボタンを押下する。
# 必要に応じて追加オプションを設定する。
# [完了]ボタンを押下する。
# [テストデータソース]で接続確認を行う。
<br>
===== Oracle用のDSN設定例 =====
# DSNの設定を行う。
#* [データソース名]
#*: DSN名を入力する。
#* [説明]
#*: 任意の説明を入力する。
#* [TNSサービス名]
#*: 接続するOracleサービスを選択する。
#* [UserID]
#*: デフォルトのユーザID (オプション)
# [OK]ボタンを押下する。
<br>
===== MySQL用のDSN設定例 =====
# DSNの設定を行う。
#* [Data Source Name]
#*: DSN名を入力する。
#* [TCP/IP Server]
#*: データベースサーバのホスト名またはIPアドレスを入力する。
#* [Port]
#*: MySQLのポート番号を入力する。(デフォルト3306番)
#* [User]
#*: データベースユーザ名を入力する。
#* [Password]
#*: パスワードを入力する。
#* [Database]
#*: 接続するデータベース名する。
# [Test]ボタンを押下して、接続テストを行う。
# [OK]ボタンを押下する。
<br>
==== エラー関連 ====
===== ドライバが表示されない場合 =====
適切なドライバをインストールする。<br>
<br>
===== 接続テストに失敗する場合 =====
ネットワーク設定および認証情報を確認する。<br>
<br>
===== データベースが表示されない場合 =====
アクセス権限を確認する。<br>
<br><br>
 
== 接続の確立 ==
データベース接続を開始するには、CDatabaseクラスのインスタンスを生成して、OpenメソッドまたはOpenExメソッドを実行する。<br>
接続文字列には、DSN、ユーザ名、パスワード等の認証情報を含めることができる。<br>
<br>
<syntaxhighlight lang="c++">
// 接続文字列の制定
CString strConnect = _T("DSN=MyDataSource;UID=user;PWD=password;");
// データベースの接続
CDatabase database;
database.OpenEx(strConnect, CDatabase::noOdbcDialog);
</syntaxhighlight>
<br><br>
 
== エラーハンドリング ==
データベースの接続エラー、クエリ実行エラー、その他の予期せぬエラー等に対応することが重要である。<br>
<br>
<syntaxhighlight lang="c++">
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("予期せぬエラーが発生"));
}
</syntaxhighlight>
<br><br>
 
== レコードの抽出 ==
  <syntaxhighlight lang="c++">
  #include <afxdb.h>
  #include <afxdb.h>
 
  void CSampleDlg::OnBnClickedQueryButton1()
  void CSampleDlg::OnBnClickedQueryButton1()
  {
  {
21行目: 183行目:
     db.OpenEx( _T( strCon ), CDatabase::noOdbcDialog );
     db.OpenEx( _T( strCon ), CDatabase::noOdbcDialog );
     // db.OpenEx( _T( strCon ), CDatabase::openReadOnly | CDatabase::noOdbcDialog ); // 読み込み専用
     // db.OpenEx( _T( strCon ), CDatabase::openReadOnly | CDatabase::noOdbcDialog ); // 読み込み専用
     // CDatabase::openReadOnly    データソースを読み取り専用で開く
     // CDatabase::openReadOnly    データソースを読み取り専用で開く
     // CDatabase::noOdbcDialog    必要な情報が提供されているかに関わらず、ODBC接続のダイアログボックスを表示しない
     // CDatabase::noOdbcDialog    必要な情報が提供されているかに関わらず、ODBC接続のダイアログボックスを表示しない
26行目: 189行目:
    
    
     // 実行
     // 実行
     CRecordset rs( &db );
     CRecordset rs(&db);
     try
     try {
    {
      // SQLコマンドの実行
       rs.Open( CRecordset::forwardOnly, _T("select * from test") );//SQLコマンドの実行
       rs.Open( CRecordset::forwardOnly, _T("select * from test") );
   
   
       CODBCFieldInfo fi;
       CODBCFieldInfo fi;
36行目: 199行目:


       //フィールド名を表示
       //フィールド名を表示
       if (!rs.IsEOF())
       if (!rs.IsEOF()) {
      {
           for(short index = 0; index < rs.GetODBCFieldCount(); index++) {
           for(short index = 0; index < rs.GetODBCFieldCount(); index++ )
          {
             rs.GetODBCFieldInfo(index, fi);
             rs.GetODBCFieldInfo(index, fi);
             TRACE("%s\n",fi.m_strName);
             TRACE("%s\n",fi.m_strName);
46行目: 207行目:
   
   
       // 値を表示
       // 値を表示
       while( !rs.IsEOF() )
       while (!rs.IsEOF()) {
      {
           for(short index = 0; index < rs.GetODBCFieldCount(); index++) {
           for(short index = 0; index < rs.GetODBCFieldCount(); index++)
          {
             CString strValue;
             CString strValue;
             rs.GetFieldValue(index, _T(strValue));
             rs.GetFieldValue(index, _T(strValue));
             TRACE("%s\n",strValue);
             TRACE("%s\n", strValue);
           }
           }
           rs.MoveNext();
           rs.MoveNext(); // 次のレコードに移動
       }
       }
     }
     }
     catch(...)
     catch (...) {
    { // SQLがエラーを発生した場合はここで捕捉
      // SQLがエラーを発生した場合はここで捕捉
      // メモリリークのように見えるがODBCドライバが自動的に解放する
      // メモリリークのように見えるがODBCドライバが自動的に解放する
     }
     }
    
    
65行目: 224行目:
     db.Close();
     db.Close();
  }
  }
  </source>
  </syntaxhighlight>
<br><br>
<br><br>
<big>'''UPDATE / INSERT / DELETE文を発行する'''</big>
 
  <source lang="cpp">
== レコードの挿入 ==
  void CSampleDlg::OnBnClickedQueryButton2()
==== 基本的な挿入 ====
  {
<syntaxhighlight lang="c++">
    // 接続文字列生成
#include <afxdb.h>
    CString strCon = "DSN=MS Access 97 Database;UID = Admin; PWD = ";
// データベース接続文字列の設定
// 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();
</syntaxhighlight>
<br>
==== 各データ型の挿入 ====
* VARCHAR / NVARCHAR
*: 文字列型 (NVARCHARはUnicode対応)
* 数値型 (INT, BIGINT, SMALLINT, TINYINT)
*: 適切な範囲の値を使用する。
* 小数点数 (DECIMAL, FLOAT)
*: 精度を考慮したフォーマット
* 日付時刻型 (DATE, DATETIME, DATETIME2, TIME)
*: COleDateTimeクラスを使用して現在日時を取得する。
*: 各日付時刻型に応じた適切なフォーマットを適用する。
* バイナリ型
*: バイト配列を16進数文字列に変換して挿入する。
*: 0xプレフィックスを付加して、SQLに組み込む。
* UNIQUEIDENTIFIER
*: GUID形式の文字列
*  Unicode対応
*: NVARCHAR型には、N'文字列'の形式で値を指定する。
*: 日本語等の全角文字も正しく扱うことが可能である。
<br>
<syntaxhighlight lang="c++">
// 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値
);
</syntaxhighlight>
<br>
==== NULL値を含む挿入 ====
NULL値を許可するカラムの場合、明示的にNULLが指定できる。<br>
NULL値を含む専用のINSERT文を追加する。<br>
<br>
  <syntaxhighlight lang="c++">
  CString strSQLWithNull;
strSQLWithNull.Format(_T("INSERT INTO SampleTable ")
    _T("(VarcharColumn, IntColumn) ")
    _T("VALUES ")
    _T("(NULL, NULL)"));
  </syntaxhighlight>
<br><br>
 
== トランザクション ==
ロールバック処理自体も失敗する可能性があるため、2重のtry-catch構造を使用することにより、ロールバック処理中のエラーも適切に処理できる。<br>
<br>
<syntaxhighlight lang="c++">
// 接続文字列の生成
CString strCon = _T("DSN=MS Access 97 Database;UID=Admin;PWD=");
CDatabase db;
bool bTransStarted = false; // トランザクションの状態を追跡するフラグ
   
   
    CDatabase db;
try {
     // 接続
     // 接続
     db.OpenEx(_T( strCon ), CDatabase::noOdbcDialog);
     if (!db.OpenEx(strCon, CDatabase::noOdbcDialog)) {
     // db.OpenEx(_T( strCon ), CDatabase::openReadOnly | CDatabase::noOdbcDialog); // 読み込み専用
      AfxMessageBox(_T("データベース接続に失敗しました"));
     // CDatabase::openReadOnly    データソースを読み取り専用で開く
      return -1;
     // CDatabase::noOdbcDialog    必要な情報が提供されているかに関わらず、ODBC接続のダイアログ ボックスを表示しない
    }
     // CDatabase::forceOdbcDialog  ODBC接続のダイアログボックスを常に表示する
     // トランザクション開始
    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.BeginTrans(); // トランザクション開始
     db.CommitTrans();
     try
     bTransStarted = false;
    {
}
      db.ExecuteSQL("create table test(a text,b text)");                  // 各種SQLコマンドの実行 複数実行可能
catch (CDBException *e) {
      // db.ExecuteSQL("insert into test(a,b) values('inaba','minoru')");  // 各種SQLコマンドの実行 複数実行可能
    // データベース固有のエラー処理
       // db.ExecuteSQL("delete from test where a='inaba'");               // 各種SQLコマンドの実行 複数実行可能
    if (bTransStarted) {
       // db.ExecuteSQL("DROP TABLE test");                                 // 各種SQLコマンドの実行 複数実行可能
       try {
          db.Rollback();
       }
      catch (...) {
          AfxMessageBox(_T("ロールバック中にエラーが発生しました"));
      }
     }
     }
     catch(...)
     { // SQLでエラーが発生した場合はここで捕捉できる
    CString strError;
      // メモリリークのように見えるがODBCドライバが自動的に解放する
    strError.Format(_T("データベースエラー: %s"), e->m_strError);
    AfxMessageBox(strError);
     e->Delete();
}
catch (...) {
     // その他の予期せぬエラー処理
    if (bTransStarted) {
      try {
          db.Rollback();
      }
      catch (...) {
          AfxMessageBox(_T("ロールバック中にエラーが発生しました"));
      }
     }
     }
 
     db.CommitTrans(); // トランザクションの完了
     AfxMessageBox(_T("予期せぬエラーが発生しました"));
 
    db.Close();
  }
  }
  </source>
// データベースを閉じる
if (db.IsOpen()) db.Close();
  </syntaxhighlight>
<br><br>
<br><br>


== 注意点 ==
== 注意点 ==
SQLServer等でSELECT文を発行すると、上記のコードではdatetime等が表示できない場合がある。<br>
SQL Server等でSELECT文を発行すると、datetime型カラム等が表示できない場合がある。<br>
そのため、GetFieldValueの引数にCDBVariantを使用する。<br><br>
そのため、GetFieldValueメソッドの引数にCDBVariantを使用する。<br>
 
<br>
<center>'''表1. CDBVariantのメンバ'''</center>
<center>. CDBVariantのメンバ</center>
<center>
<center>
{| class="wikitable"
{| class="wikitable"
135行目: 495行目:
</center>
</center>
<br>
<br>
上記に記載したCDBVariant::m_dwTypeにおいて、<br>
上表のCDBVariant::m_dwTypeでは、CDBVariantオブジェクトの共用体データメンバに現在格納されている値のデータ型が入る。<br>
CDBVariantオブジェクトの共用体データメンバに現在格納されている値のデータ型が入る。<br>
共用体にアクセスする前に、共用体のどのメンバにアクセスするかを決めるために、m_dwTypeの値をチェックする必要がある。<br>
共用体にアクセスする前に、共用体のどのメンバにアクセスするかを決めるために、m_dwTypeの値をチェックする必要がある。<br>
次の表は、m_dwTypeの値と対応する共用体メンバの一覧である。<br>
<br>
 
下表は、m_dwTypeの値と対応する共用体メンバの一覧である。<br>
<center>'''表1. m_dwType共用体メンバ'''</center>
<br>
<center>. m_dwType共用体メンバ</center>
<center>
<center>
{| class="wikitable"
{| class="wikitable"
168行目: 528行目:
</center>
</center>
<br>
<br>
 
  <syntaxhighlight lang="cpp">
  <source lang="cpp">
  CString strCon = "DSN = SQLServerTest;";
  CString strCon = "DSN = SQLServerTest;";
  CDatabase db;
  CDatabase db;
177行目: 536行目:
  try
  try
  {
  {
     rs.Open( CRecordset::forwardOnly, _T("select * from test") );//SQLコマンドの実行
     rs.Open( CRecordset::forwardOnly, _T("select * from test") ); // SQLコマンドの実行
     // 値を表示
     // 値を表示
     while(!rs.IsEOF())
     while (!rs.IsEOF()) {
    {
       for(short index = 0; index < rs.GetODBCFieldCount(); index++) {
       for(short index = 0; index < rs.GetODBCFieldCount(); index++)
      {
           CDBVariant val;
           CDBVariant val;
           rs.GetFieldValue( index,val);
           rs.GetFieldValue(index, val);
   
   
           switch(val.m_dwType)
           switch(val.m_dwType) {
          {
             case DBVT_NULL :
             case DBVT_NULL :
                 TRACE("NULL 無効の値\n");
                 TRACE("NULL 無効の値\n");
217行目: 574行目:
             case DBVT_DATE :
             case DBVT_DATE :
                 TRACE("m_pdate TIMESTAMP_STRUCT 型のオブジェクトへのポインタ\n");
                 TRACE("m_pdate TIMESTAMP_STRUCT 型のオブジェクトへのポインタ\n");
                 TIMESTAMP_STRUCT *pT;
                pT = val.m_pdate;
                // NULLチェック
                TRACE("%d/%d/%d %d:%d:%d\n",pT->year,pT->month,pT->day,pT->hour,pT->minute,pT->second );
                 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;
                 break;
             case DBVT_STRING :
             case DBVT_STRING :
                 TRACE("m_pstring CString 型のオブジェクトへのポインタ\n");
                 if (val.m_pstring != nullptr) {
                TRACE("%s\n",*val.m_pstring);
                  TRACE("m_pstring CString 型のオブジェクトへのポインタ\n");
                  TRACE(_T("Field: %s, Value: %s\n"), fieldName, *val.m_pstring);
                }
                 break;
                 break;
             case DBVT_BINARY :
             case DBVT_BINARY :
236行目: 600行目:
     }
     }
  }
  }
  catch(...)
catch (CDBException *e) {
{
    TRACE(_T("Database error: %s\n"), e->m_strError);
    e->Delete();
}
  catch (...) {
    TRACE(_T("不明なデータベースエラーが発生\n"));
  }
  }
   
   
  rs.Close();
  rs.Close();
  db.Close();
  db.Close();
  </source>
  </syntaxhighlight>
<br><br>
<br><br>


__FORCETOC__
__FORCETOC__
[[カテゴリ:MFC]]
[[カテゴリ:MFC]]

2025年1月12日 (日) 07:27時点における最新版

概要

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