「MFCとデータベース - CRUDの実行」の版間の差分
細 (Wiki がページ「ODBCでデータベースを更新する」を「ODBCでデータベースを更新する(MFC)」に、リダイレクトを残さずに移動しました) |
|||
(同じ利用者による、間の16版が非表示) | |||
1行目: | 1行目: | ||
== 概要 == | == 概要 == | ||
MFCにおけるデータベースアクセスは、<code>CDatabase</code>クラスと<code>CRecordset</code>クラスを中心に構築される。<br> | |||
これらのクラスはODBCを介して、様々なデータベースと通信を行う。<br> | |||
# | <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アドミニストレータでのデータソース登録 == | ||
< | データソースを登録することにより、アプリケーションからDSN名を使用してデータベースに接続することができる。<br> | ||
< | <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") ); | 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がエラーを発生した場合はここで捕捉 | |||
// メモリリークのように見えるがODBCドライバが自動的に解放する | |||
} | } | ||
65行目: | 224行目: | ||
db.Close(); | db.Close(); | ||
} | } | ||
</ | </syntaxhighlight> | ||
<br><br> | <br><br> | ||
< | |||
< | == レコードの挿入 == | ||
==== 基本的な挿入 ==== | |||
<syntaxhighlight lang="c++"> | |||
#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(); | |||
</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; // トランザクションの状態を追跡するフラグ | |||
try { | |||
// 接続 | // 接続 | ||
db.OpenEx | if (!db.OpenEx(strCon, CDatabase::noOdbcDialog)) { | ||
// db. | 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. | db.CommitTrans(); | ||
bTransStarted = false; | |||
} | |||
catch (CDBException *e) { | |||
// データベース固有のエラー処理 | |||
if (bTransStarted) { | |||
try { | |||
db.Rollback(); | |||
} | |||
catch (...) { | |||
AfxMessageBox(_T("ロールバック中にエラーが発生しました")); | |||
} | |||
} | } | ||
catch(...) | |||
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(); | |||
</syntaxhighlight> | |||
<br><br> | <br><br> | ||
== 注意点 == | == 注意点 == | ||
SQL Server等でSELECT文を発行すると、datetime型カラム等が表示できない場合がある。<br> | |||
そのため、GetFieldValueメソッドの引数にCDBVariantを使用する。<br> | |||
<br> | |||
<center> | <center>表. CDBVariantのメンバ</center> | ||
<center> | <center> | ||
{| class="wikitable" | {| class="wikitable" | ||
135行目: | 495行目: | ||
</center> | </center> | ||
<br> | <br> | ||
上表のCDBVariant::m_dwTypeでは、CDBVariantオブジェクトの共用体データメンバに現在格納されている値のデータ型が入る。<br> | |||
共用体にアクセスする前に、共用体のどのメンバにアクセスするかを決めるために、m_dwTypeの値をチェックする必要がある。<br> | 共用体にアクセスする前に、共用体のどのメンバにアクセスするかを決めるために、m_dwTypeの値をチェックする必要がある。<br> | ||
<br> | |||
下表は、m_dwTypeの値と対応する共用体メンバの一覧である。<br> | |||
<center> | <br> | ||
<center>表. m_dwType共用体メンバ</center> | |||
<center> | <center> | ||
{| class="wikitable" | {| class="wikitable" | ||
168行目: | 528行目: | ||
</center> | </center> | ||
<br> | <br> | ||
<syntaxhighlight 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 * | |||
// 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; | break; | ||
case DBVT_STRING : | case DBVT_STRING : | ||
TRACE("m_pstring CString 型のオブジェクトへのポインタ\n"); | if (val.m_pstring != nullptr) { | ||
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(); | ||
</ | </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つの項目からなる。
- 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();