|
SQL 타입 ID |
SQL 데이터 타입 |
SQL_CHAR |
CHAR |
SQL_VARCHAR |
VARCHAR |
SQL_DECIMAL |
DECIMAL |
SQL_SMALLINT |
SMALLINTEGER |
SQL_INTEGER |
INTGER |
SQL_BINARY |
BINARY |
SQL_TYPE_DATE |
DATE |
(DBMS와 대응되는 타입만 지원)
-C언어 데이터타입
C형 타입은 응용 프로그램에서 DB로부터 결과셋을 돌려받을 때나 버퍼의 타입을 지정할 때 사용하는데 C언어의 데이터 타입들이므로 이해하기 쉬울 것이다. 모든 드라이버들은 DBMS의 데이터를 응용 프로그램이 원하는대로 변환하기 위해 이 C타입을 모두 지원해야 한다. 다음 표는 ODBC의 C 데이터 타입 ID와 각 타입의 정의 및 대응되는 C언어의 데이터 타입을 보인 것이다.
C 타입 ID |
typedef |
C 데이터 타입 |
SQL_C_CHAR |
SQLCHAR* |
unsigned char* |
SQL_C_SSHORT |
SQLSMALLINT |
short int |
SQL_C_USHORT |
SQLUSMALLINT |
unsigned short int |
SQL_C_SLONG |
SQLINTEGER |
long int |
SQL_C_ULONG |
SQLUINTEGER |
unsigned long int |
SQL_C_FLOAT |
SQLREAD |
float |
SQL_C_DOUBLE |
SQLDOUBLE |
double |
SQL_C_BIT |
SQLSCHAR |
unsigned char |
SQL_C_BINARY |
SQLCHAR* |
unsigned char* |
SQL_C_BOOKMARK |
BOOKMARK |
unsigned long int |
SQL_C_TYPE_DATE |
SQL_DATE_STRUCT |
struct tagDATE_STRUCT{ SQLSMALLINT year; SQLUSMALLINT month; SQLUSMALLINT day; |
핸들
ODBC는 정보 저장을 위해 핸들을 사용한다. HDC, HWND 등의 일반적인 핸들과 사용하는 목적이나 방법은 동일하다. ODBC는 환경, 연결, 명령, 설명자의 4가지 핸들을 사용하는데 이중 설명자 핸들은 잘 사용되지 않는다.
SQLHENV hEnv; 환경핸들
SQLHDBC hDbc; 연결핸들
SQLHSTMT hStmt; 명령핸들
SQLHDESC hDes; 설명자핸들
각 핸들은 다른 타입으로 정의되어 있지만 모드 void * 형이다. 이 핸들은 불투명한 데이터형이므로 내부가 어떻게 정의되어 있는지는 몰라도 상관없다. 우리는 이때까지 그랬던 것처럼 핸들을 얻고 적당한 위치에서 사용하다가 다 쓰고 난 후 핸들을 해제하면 된다.
-핸들 할당함수
SQLRETURN SQLAllocHandle( SQLSMALLINT HandleType, //할당하고자 하는 핸들의 타입 SQLHANDLE InputHandle, //새 핸들이 포함될 부모 핸들을 지정 SQLHANDLE* OutputHandlePtr //새로 만들어질 핸들의 번지를 ); 지정하는 출력용 인수 |
HandleType : SQL_HANDLE_ENV (환경) , SQL_HANDLE_DBC (연결), SQL_HANDLE_STMT (명령), SQL_HANDLE_DESC (설명자)
InputHandle : 핸들이 포함될 부모 핸들
OutputHandlePtr : 만들어질 핸들의 번지를 지정
-핸들 해제함수
핸들을 다 사용하고 난 후에는 이 함수로 핸들을 반드시 해제해야 한다.
SQLRETURN SQLFreeHandle( SQLSMALLINT Handle Type, //해제하고자 하는 핸들의 타입 SQLHANDLE Handle //해제 할 핸들 ); |
HandleType : SQL_HANDLE_ENV (환경) , SQL_HANDLE_DBC (연결), SQL_HANDLE_STMT (명령), SQL_HANDLE_DESC (설명자)
-환경 핸들
환경(Environment)에는 데이터 액세스에 필요한 여러 가지 정보들이 저장되는데 이 정보들은 본질적으로 전역적이다. 환경의 현재 상태, 진단 정보, 환경의 속성등이 환경에 저장되는 정보들이며 무엇보다 중요한 것은 연결에 관련된 정보들이다.
SQLSetEnvAttr(SQLHENV EnvironmentHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength); |
1번째 인자 : 환경핸들
2번째 인자 : 설정하고자 하는 속성의 종류
3번째 인자 : 속성 값
4번째 인자 : 속성값의 길이
예제)
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE,&hEnv); SQLSetEnvAttr(hEnv,SQL_ATTR_ODBC_VERSION,(SQLPOINTER)SQL_OV_ODBC3,SQL_IS_INTEGER); |
hEnv핸들에 환경 핸들을 할당하고 이 핸들의 버전을 3.0으로 설정하였다. 핸들의 버전 설정을 생략하면 이 후 어떠한 ODBC API 호출도 할 수 없다. 이 두 줄은 특별한 이유가 없는 한 이대로 가져다 쓰면 된다.
-연결핸들
연결(Connection)은 사용할 데이터 소스와 드라이버에 대한 정보를 가지며 연결 핸들에는 연결의 상태, 진단 정보, 명령 핸들과 설명자 핸들의 목록, 연결의 속성 등의 정보가 포함된다.
예제)
SQLAllocHandle(SQL_HANDLE_DBC, hEnv,&hDbc); |
연결핸들 hDbc를 할당하고 hEnv 환경 안에 그 정보를 저장한다.
-연결
연결 핸들을 할당하고 난 후에 이 핸들을 사용하여 실제 데이터 소스와 연결한다. 연결 함수는 세가지 종류가 있는데 SQLConnect가 가장 간단하고 그 외에 SQLDriverConnect, SQLBrowseConnect 등의 함수가 있는데 여기서는 다소 복잡한 SQLDriverConnect 함수로 연결하였다. 이 함수는 좀 복잡하지만 별도의 환경 설정없이 파일의 경로를 지정하는 방식으로 연결 할 수 있기 때문에 처음 배우는 사람에게는 오히려 더 쉽다. 원형은 다음과 같다.
SQLConnect, SQLDriverConnect, SQLBrowseconnect SQLRETURN SQLDriverConnect( SQLHDBC ConnectionHandle, //할당한 연결 핸들 SQLHWND WindowHandle, //메인 윈도우의 핸들 SQLCHAR *InconnectionString, //연결정보를 지정하는 연결 문자열 SQLSMALLINT StringLength1 , //연결 문자열의 길이 SQLCHAR *OutConnectionString, //함수가 실행 중에 구성한 연결 문자열을 돌려받기 위한 문자열의 버퍼 SQLSMALLINT BufferLength, //버퍼의 길이 SQLSMALLINT *StringLength2Ptr, //문자열 길이의 번지 SQLUSMALLINT DriverCompletion //드라이버 옵션 ); à 5~8번째 인자는 정보가 불완전할 경우 사용자가 추가 정보를 대화상자로 요구하고 완성된 문자열을 리턴할 때 쓰인다. (참고) 연결문자열이란, 데이터 소스에 연결하기 위한 정보를 가지는 문자열 |
예제)
GetCurrentDirectory(MAX_PATH,Dir);
wsprintf((TCHAR *)InCon,L"DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s\\Stu.mdb;",Dir);
SQLDriverConnect(hDbc,hDlgMain,InCon,sizeof(InCon),(SQLWCHAR*)OutCon,sizeof(OutCon),&cbOutCon, SQL_DRIVER_NOPROMPT); |
-명령핸들
명령 핸들을 할당한 후 곧바로 명령을 실행할 수 있다. 명령을 실행하는 방법에는 크게 4가지가 있지만 제일 간단한 방법은 명령 핸들에 곧바로 명령을 보내주는 것이다. 다음 함수를 사용한다.
SQLRETURN SQLExecDirect( SQLHSTMT StatementHandle, //명령핸들 SQLCHAR* StatemnetText, //실행할 SQL문 SQLINTEGER TextLength //SQL문의 길이 ); |
예제)
TCHAR szSQL[256]; wsprintf(szSQL, "Insert into Stu (name, age) VALUES ('%s', %d)", Name, Age); -검색 à SQLExecDirect(hStmt,(SQLCHAR*)“select name from Stu”,SQL_NTS); -추가 à “Insert into Stu (name, age) VALUES (‘%s’,%d)”,Name,Age); -삭제 à “Delete from Stu where name=‘%s’”
|
szSQL변수에 SQL문을 할당한 후 SQLExecDirect에서 사용하는 예제
-바인딩
결과셋의 컬럼과 변수를 연결하는 동작을 바인딩(Binding)이라고 한다.
SQLRETURN SQLBindCol( SQLHSTMT StatementHandle, //명령 핸들 SQLUSMALLINT ColumnNumber, //바인딩 될 컬럼의 번호 SQLSMALLINT TargetType, //바인딩 되는 변수의 데이터 타입 SQLPOINTER TargetValuePtr, //저장할 버퍼 SQLINTEGER BufferLength, //버퍼의 길이 SQLLEN* StrLen_or_lnd //컬럼의 길이나 상태를 리턴 |
예제)
SQLINTEGER Iname,IAge; //변수 SQLBindCol(hStmt,1,SQL_C_CHAR,Name,sizeof(Name),&IName); SQLBindCol(hStmt,2,SQL_C_ULONG,&Age,0,&IAge); |
-데이터 가져오기
결과셋에서 실제로 데이터를 가져올 때는 다음 함수를 호출한다. 명령 핸들만 인수로 전달하면 된다.
SQLRETURN SQLFetch(SQLHSTMT StatementHandle); //명령핸들 |
예제)
while (SQLFetch(hStmt)!=SQL_NO_DATA) { //SQL_NO_DATA 를 반복적으로 호출하면 결과 셋의 모든 레코드를 읽을 수 있다. } |
-마무리 작업들
열려진 모든 핸들을 닫고 뒷정리를 하는 작업이다.
SQLCloseCursor(hStmt); //명령핸들에 열려진 커서를 닫고 결과셋을 모두 버리는 함수 (명령을 재사용하려면 반드시 결과셋을 먼저 버리고 재사용 해야함) SQLFreeHandle(); //할당된 핸들을 해제하는 함수 (할당된 순서의 역순으로 해제) SQLDisconnect(hDbc); //데이터 소스와의 연결을 끊음 |
예제)
SQLFreeHandle(SQL_HANDLE_STMT,hStmt); SQLDisconnect(hDbc); SQLFreeHandle(SQL_HANDLE_DBC,hDbc); SQLFreeHandle(SQL_HANDLE_ENV,hEnv);
|
-실습
삽입, 삭제, 나이로 검색버튼이 동작하도록 코드 작성하기
Database.h
#pragma once
#include <windows.h> #include <sql.h> #include <sqlext.h>
class Database { static Database *database; HWND hDlg; SQLHENV hEnv; //환경 SQLCHAR InCon[255]; public: static Database *GetDatabase(HWND _hDlg); static void ClearDatabase();
bool Insert(SQLCHAR* Name, int age); void Del(SQLCHAR* Name); BOOL SearchAll(HWND hList); void DBSearchByAge(HWND hList,int age); private: Database(HWND _hDlg); ~Database(void); bool DBSetting(HWND hDlg); SQLHDBC DBConnect(); void DBDisConnect(SQLHDBC hDbc); void DBRelease(); };
|
Database.cpp
#include "Database.h"
Database *Database::database; Database::Database(HWND _hDlg) { hDlg = _hDlg;
if (DBSetting(hDlg) == FALSE) { MessageBox(hDlg,"데이터베이스에연결할수없습니다","에러",MB_OK); } }
Database::~Database(void) { DBRelease(); }
Database *Database::GetDatabase(HWND _hDlg) { if(!database) { database = new Database(_hDlg); } return database; }
void Database::ClearDatabase() { if(database) { delete database; database = 0; } }
bool Database::Insert(SQLCHAR* Name,int age) { SQLHDBC hDbc = DBConnect(); SQLHSTMT hStmt;
if (SQLAllocHandle(SQL_HANDLE_STMT,hDbc,&hStmt) != SQL_SUCCESS) { return false; }
TCHAR szSQL[256];
wsprintf(szSQL,"Insert into Stu (m_name,age) VALUES ('%s',%d)",Name,age); if (SQLExecDirect(hStmt,(SQLCHAR *)szSQL,SQL_NTS) != SQL_SUCCESS) { DBDisConnect(hDbc); return false; }
SQLFreeHandle(SQL_HANDLE_STMT,hStmt); DBDisConnect(hDbc); return true; }
void Database::Del(SQLCHAR* Name) { SQLHDBC hDbc = DBConnect(); SQLHSTMT hStmt;
if (SQLAllocHandle(SQL_HANDLE_STMT,hDbc,&hStmt) != SQL_SUCCESS) { return ; }
TCHAR szSQL[256+1]; wsprintf(szSQL,"Delete from Stu where m_name='%s'",Name);
if(SQLExecDirect(hStmt,(SQLCHAR *)szSQL,SQL_NTS) != SQL_SUCCESS) { return; } SQLFreeHandle(SQL_HANDLE_STMT,hStmt); DBDisConnect(hDbc); }
bool Database::DBSetting(HWND hDlg) { TCHAR Dir[MAX_PATH];
// 환경핸들을할당하고버전속성을설정한다. if (SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&hEnv) != SQL_SUCCESS) return FALSE; if (SQLSetEnvAttr(hEnv,SQL_ATTR_ODBC_VERSION,(SQLPOINTER)SQL_OV_ODBC3,SQL_IS_INTEGER) != SQL_SUCCESS) return FALSE;
// 연결문자열설정 GetCurrentDirectory(MAX_PATH,Dir); wsprintf((TCHAR *)InCon,"DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s\\Stu.mdb;",Dir);
return TRUE; } SQLHDBC Database::DBConnect() { SQLHDBC hDbc; SQLRETURN Ret; SQLCHAR OutCon[1024]; SQLSMALLINT cbOutCon; if (SQLAllocHandle(SQL_HANDLE_DBC,hEnv,&hDbc) != SQL_SUCCESS) return INVALID_HANDLE_VALUE; Ret=SQLDriverConnect(hDbc,hDlg,InCon,sizeof(InCon),OutCon,sizeof(OutCon),&cbOutCon, SQL_DRIVER_NOPROMPT);
if ((Ret != SQL_SUCCESS) && (Ret != SQL_SUCCESS_WITH_INFO)) return INVALID_HANDLE_VALUE; return hDbc; }
void Database::DBDisConnect(SQLHDBC hDbc) { // 뒷정리 if (hDbc) SQLDisconnect(hDbc); if (hDbc) SQLFreeHandle(SQL_HANDLE_DBC,hDbc);
}
void Database::DBRelease() { if (hEnv) SQLFreeHandle(SQL_HANDLE_ENV,hEnv); }
BOOL Database::SearchAll(HWND hList) { SQLHDBC hDbc = DBConnect(); SQLHSTMT hStmt;
if (SQLAllocHandle(SQL_HANDLE_STMT,hDbc,&hStmt) != SQL_SUCCESS) { return FALSE; }
// 결과값을돌려받기위한변수들 SQLCHAR Name[21]; SQLINTEGER lName; // 결과를돌려받기위해바인딩한다. SQLBindCol(hStmt,1,SQL_C_CHAR,Name,sizeof(Name),&lName);
// SQL문을실행한다. if (SQLExecDirect(hStmt,(SQLCHAR *)"select m_name from Stu",SQL_NTS) != SQL_SUCCESS) { return false; }
// 읽어온데이터출력 SendMessage(hList,LB_RESETCONTENT,0,0); while (SQLFetch(hStmt)!=SQL_NO_DATA) { SendMessage(hList,LB_ADDSTRING,0,(LPARAM)Name); }
SQLFreeHandle(SQL_HANDLE_STMT,hStmt); DBDisConnect(hDbc); return TRUE; }
#include <stdio.h> void Database::DBSearchByAge(HWND hList,int age) { SQLHDBC hDbc = DBConnect(); SQLHSTMT hStmt;
if (SQLAllocHandle(SQL_HANDLE_STMT,hDbc,&hStmt) != SQL_SUCCESS) { return ; }
// 결과값을돌려받기위한변수들 SQLCHAR Name[21]; SQLINTEGER lName; // 결과를돌려받기위해바인딩한다. SQLBindCol(hStmt,1,SQL_C_CHAR,Name,sizeof(Name),&lName);
char querystr[256]; sprintf(querystr,"select m_name from Stu where age=%d",age); // SQL문을실행한다. if (SQLExecDirect(hStmt,(SQLCHAR *)querystr,SQL_NTS) != SQL_SUCCESS) { return ; }
// 읽어온데이터출력 SendMessage(hList,LB_RESETCONTENT,0,0); while (SQLFetch(hStmt)!=SQL_NO_DATA) { SendMessage(hList,LB_ADDSTRING,0,(LPARAM)Name); }
SQLFreeHandle(SQL_HANDLE_STMT,hStmt); DBDisConnect(hDbc); return; } |
Foo.cpp
#include <windows.h> #include "resource.h" #include "Database.h" BOOL CALLBACK DlgProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam); INT APIENTRY WinMain(HINSTANCE hIns,HINSTANCE hPrev,LPSTR lpCmd,int nShow) { DialogBox(hIns,MAKEINTRESOURCE(IDD_DIALOG1),0,DlgProc); return 0; }
void OnInit(HWND hDlg); void OnCommand(HWND hDlg,WORD cid,WORD cmsg,HWND cWnd);
BOOL CALLBACK DlgProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam) { switch(iMessage) { case WM_INITDIALOG: OnInit(hDlg); return TRUE; case WM_COMMAND: OnCommand(hDlg,LOWORD(wParam),HIWORD(wParam),(HWND)lParam);return TRUE; }
return FALSE; } void PrintList(HWND hDlg); void OnInit(HWND hDlg) { PrintList(hDlg); }
void ExitProc(HWND hDlg); void Add(HWND hDlg); void Del(HWND hDlg); void Search(HWND hDlg); void OnCommand(HWND hDlg,WORD cid,WORD cmsg,HWND cWnd) { switch(cid) { case IDC_ADD: Add(hDlg); return; case IDC_DEL: Del(hDlg); return; case IDB_SEARCH: Search(hDlg); return; case IDCANCEL: ExitProc(hDlg); return; } }
void ExitProc(HWND hDlg) { EndDialog(hDlg,0); }
void Add(HWND hDlg) { TCHAR Name[20];
GetDlgItemText(hDlg,IDE_NAME,Name,sizeof(Name)); if (lstrlen((LPCTSTR)Name) == 0) { MessageBox(hDlg,"추가할레코드의이름을입력하십시요","알림",MB_OK); return; }
int age = GetDlgItemInt(hDlg,IDE_AGE,0,false); if (lstrlen((LPCTSTR)Name) == 0) { MessageBox(hDlg,"추가할레코드의나이를입력하십시요","알림",MB_OK); return; }
Database *database = Database::GetDatabase(hDlg); if(database->Insert((SQLCHAR*)Name,age)) { MessageBox(hDlg,"가입성공","알림",MB_OK); } else { MessageBox(hDlg,"중복된이름이있습니다","알림",MB_OK); } PrintList(hDlg); }
void Del(HWND hDlg) { TCHAR Name[256+1]; int idx;
HWND hList = GetDlgItem(hDlg,IDL_LIST); idx= SendMessage(hList,LB_GETCURSEL,0,0); if (idx == -1) { MessageBox(hDlg,"삭제할레코드를선택하세요","알림",MB_OK); return; } SendMessage(hList,LB_GETTEXT,idx,(LPARAM)Name);
Database *database = Database::GetDatabase(hDlg); database->Del((SQLCHAR*)Name); PrintList(hDlg); } void Search(HWND hDlg) { Database *database = Database::GetDatabase(hDlg);
HWND hList; hList = GetDlgItem(hDlg,IDL_LIST); int age = (int)GetDlgItemInt(hDlg,IDE_AGE2,0,0);
database->DBSearchByAge(hList,age); }
void PrintList(HWND hDlg) { Database *database = Database::GetDatabase(hDlg);
HWND hList, hName, hAge; hList = GetDlgItem(hDlg,IDL_LIST); hName = GetDlgItem(hDlg,IDE_NAME); hAge = GetDlgItem(hDlg,IDE_AGE);
database->SearchAll(hList);
SetWindowText(hName,""); SetWindowText(hAge,""); } |
ú 참고문헌
*윈도우즈 API 정복 2 [한빛미디어 김상형]
|