ProudDB 에 같이 들어있는 ADO Wrapper API 는 Microsoft ActiveX Data Object (ADO)를 C++ 언어에서 쉽게 다룰 수 있도록 만들어진 API 입니다. ADO Wrapper API 를 이용하면 보다 쉽게 ADO 를 이용한 DBMS 접근을 할 수 있습니다. 예를 들어, 웹 게시판 등 ProudDB 데이터베이스 이외의 기타 용도의 데이터베이스를 접근하는 데 사용할 수 있습니다.
7. ADO Wrapper 예제은 ADO wrapper 를 사용하는 예제 프로그램입니다.
주의: ADO Wrapper API 는 ProudDB의 자체적 DB cache 기능을 쓰지 않습니다. 오로지 MS ADO 에서 제공되는 수준에서의 caching, pooling만을 사용합니다.
ADO Wrapper API 는 다음과 같은 클래스들로 구성되어 있습니다.
Proud.CAdoConnection는 데이터베이스 연결을 하는 용도로 쓰입니다.
Proud.CAdoCommand는 주로 Stored procedure 를 호출하는 용도로 쓰입니다.
Proud.CAdoRecordset는 주로 레코드를 입출력하기 위한 용도로 쓰입니다.
2.1ADO로 데이터베이스에 접근하기
DB 에 접근하려면 먼저 Proud.CAdoConnection 객체를 생성한 후 Open 메서드를 씁니다. 그리고 나서 Proud.CAdoConnection.Execute 로 DB 에 쿼리를 던질 수 있습니다.
void Foo() { Proud::CAdoConnection conn; conn.Open(L"Data Source=localhost;Database=ProudDB-Test;Trusted_Connection=yes"); conn.BeginTrans(); // 트랜잭션 시작 int Val = 3; conn.Execute(L"delete from Table1 where FieldA=%d",Val); conn.CommitTrans(); // 트랜잭션 Commit }
2.2ADO로 recordset 에 접근하기
실행한 쿼리 결과에서 recordset 을 얻으려면 Proud.CAdoRecordset 객체를 생성한 후 Proud.CAdoRecordset.Open 을 호출하면 됩니다. 그리고 필드를 다루기 위해 Proud.CAdoRecordset.FieldValues, Proud.CAdoRecordset.FieldNames 를 다루면 됩니다.
void Foo() { Proud::CAdoConnection conn; ... Proud::CAdoRecordset rs; rs.Open(conn,OpenForReadWrite,L"select * from Table1 where FieldA=%d",Val); rs.FieldValues[L"FieldA"] = Val+1; rs.Update(); rs.Close(); rs.Open(conn,OpenForReadWrite,L"select * from Table2",Val); while(rs.IsEOF() == false) { int a = rs.FieldValues[L"FieldX"]; rs.MoveNext(); } }
2.3ADO로 stored procedure 를 실행하기
ADO로 stored procedure를 호출하는 방법
Stored procedure 를 실행하려면 Proud.CAdoCommand 객체를 생성한 후 Proud.CAdoCommand.Parameters 필드에 파라메터들을 채운 후 Proud.CAdoCommand.Execute를 호출하면 됩니다.
void Foo() { Proud::CAdoConnection conn; ... Proud::CAdoCommand cmd; cmd.Prepare(conn, L"sp_GetTable1"); cmd.Parameters[1] = Val; // stored proc의 첫번째 파라메터를 의미 Proud::CAdoRecordset rs = cmd.Execute(); long retVal = cmd.Parameters[0]; // stored proc 가 리턴한 값을 얻는다. if(retVal < 0) { ... } else { if(!rs.IsOpened()) rs.Open(); // 리턴받은 recordset 객체를 먼저 열어야 접근이 가능합니다. int x = rs.FieldValues[L"FieldA"]; } }
ADO Wrapper API는 오류가 발생시 Proud.AdoException 타입(std.exception 의 파생 클래스입니다)의 예외를 던집니다. 아래는 예외를 처리하는 예입니다.
void Foo() { try { Proud::CAdoConnection conn; ... } catch(Proud::AdoException &e) { ShowException(Proud::StringA2W(e.what())); // e.what()이 ASCII형 문자열을 리턴하므로 유니코드로 변환합니다. } }
ADO로 stored procedure를 호출할 때 SP의 output parameter를 이용하는 방법
void Foo() { int val = -1; Proud::CAdoConnection conn; ... Proud::CAdoCommand cmd; cmd.Prepare(conn, _PNT("sp_GetTable1")); ADODB::_Parameter *p = cmd.AppendParameter(_PNT("@ParameterName"), ADODB::adInteger, ADODB::adParamOutput, sizeof(int)); cmd.Execute(); printf("The SP output parameter value : %d\n", (int)p->GetValue()); }
2.4ADO로 사용 가능한 데이터 타입
2. ADO Wrapper API는 대부분의 데이터 타입과 호환됩니다.
아래는 그 예입니다.
Proud::CAdoCommand cmd; ... cmd.Parameters[1] = 3.f; // 부동소수점 입력 cmd.Parameters[2] = L"abc"; // 문자열 입력 cmd.Parameters[3] = CTime(2009,1,1,3,4,5); // 날짜시간 입력. 2009년 1월 1일 3시 4분 5초 ... int ret = cmd.Parameters[0]; // 정수 출력 Proud::String txt = cmd.Parameters[2]; // 문자열 출력 CTime when = cmd.Parameters[3]; // 날짜시간 출력
하지만 몇 가지 데이터 타입은 별도의 모듈 사용을 필요로 합니다. 이들은 다음과 같습니다.
날짜시간 데이터 타입
2. ADO Wrapper API를 쓰는 과정에서 날짜시간 데이터 타입을 위해 제공되는 클래스로 Proud.CPnTime 이 있습니다. 아래는 사용 예입니다.
Proud::CPnTime t(2009,1,2,3,4,5); // 2009년 1월 2일 3시 4분 5초 Proud::CAdoCommand cmd; cmd.Parameters[1] = t; // stored procedure의 파라메터로 날짜시간을 입력 ... Proud::CPnTime t2; t2 = cmd.Parameters[2]; // stored procedure의 출력 파라메터로써 날짜시간을 받습니다. t2.Format(L"%x"); // 날짜시간을 문자열로 변환합니다. int year = t2.GetYear(); // 연도 구하기
Image 데이터 타입
2. ADO Wrapper API를 쓰는 과정에서 Image 데이터 타입은 다음과 같이 다룰 수 있습니다.
Proud::CAdoCommand cmd; Proud::ByteArray t; ... // t 에 데이터를 채움 cmd.Parameters[1].FromByteArray(t); // t 를 stored procedure의 파라메터로 넣기 ... ByteArray t2; t2 = cmd.Parameters[2].ToByteArray(t2); // stored procedure의 출력 파라메터로써 Image type 값을 얻어옵니다.
2.5MySQL 사용시 ADO로 접근
2. ADO Wrapper API는 MySQL 에도 접근이 가능합니다.
MySQL 에 접근하기 위해선 먼저 MySQL ODBC Driver 혹은 MySQL Provider 를 설치 하셔야 합니다.
아래의 예는 드라이버 설치후 연결하는 예 입니다.
void Foo() { Proud::CAdoConnection conn; conn.Open(L"Driver={MySQL ODBC 5.1 Driver}; server=localhost;port=3306;Database=tablename; User ID=xxx;Password=yyy;"); //ODBC Driver 5.1사용시 //conn.Open(L"Provider=MySQLProv;server=localhost;port=3306;Database=tablename;User ID=xxx;Password=yyy;");Provider 을 사용하는경우. //conn.Open(L"Driver={MySQL ODBC 3.51 Driver};server=localhost;port=3306;Database=tablename;User ID=xxx;Password=yyy;");ODBC Driver 3.51사용시 //conn.Open(L"Driver={MySQL ODBC 5.1 Driver};server=localhost;port=3306;Database=tablename;User ID=xxx;Password=yyy;",MySql);enum값으로 넘겨 string검사 생략. conn.BeginTrans(); // 트랜잭션 시작 주의!!Provider 를 사용할 경우 트랜잭션 지원하지 않음. int Val = 3; conn.Execute(L"delete from Table1 where FieldA=%d",Val); conn.CommitTrans(); // 트랜잭션 Commit }
하지만 MySQL 은 MSSQL과 다르게 몇가지 주의 할점이 있습니다.이들은 다음과 같습니다.
MySQL 에서 ADO Wrapper API 를 사용시 주의점
2. ADO Wrapper API를 MySQL 에서 쓰는 과정에서는 몇가지 주의 할점이 있습니다.
Provider 를 사용하는 경우(ConnectionString 에 Provider=MySQLProv;가 들어 가는 경우)
트랜잭션을 지원하지 않습니다.이는 MySQL자체가 트랜잭션을 지원하지 않기 때문 입니다.
허나,ODBC 드라이버를 사용하는 경우는 트랜잭션을 사용하셔도 무방합니다.드라이버가 지원하기 때문입니다.
Proud.CAdoCommand 를 사용하는 경우
Parameters 를 명시적으로 넣어 주셔야 합니다.
또한 리턴값이 없고, out Parameter 을 지원하지 않으므로 주의 하셔야 합니다.
아래예는 MySQL 에서 Proud.CAdoCommand 사용법 입니다.
void Foo() { Proud::CAdoConnection conn; Proud::CAdoRecordset rs; ... Proud::CAdoCommand cmd; cmd.Prepare(conn, L"sp_GetTable1"); // cmd.Parameters[1] = Val; // stored proc의 첫번째 파라메터를 의미 이것을 사용하면 안됩니다. cmd.AppendParameter(L"ParamName",ADODB::adVarWChar,ADODB::adParamInput,Val); //파라메터를 명시적으로 넣어 주셔야 합니다. cmd.Execute(rs); //long retVal = cmd.Parameters[0]; // stored proc 가 리턴한 값을 얻는다. MySql 은 리턴값을 지원하지 않습니다. if(!rs.IsOpened()) rs.Open(); // 리턴받은 recordset 객체를 먼저 열어야 접근이 가능합니다. int x = rs.FieldValues[L"FieldA"]; }
2.6ADO 작동 추적하기
2. ADO Wrapper API은 DBMS 를 접근하는 과정 하나 하나를 추적하는 기능을 제공하고 있습니다.
MS SQL Server 등의 DBMS 는 쿼리 추적기 (혹은 쿼리 프로필러)를 제공하고 있습니다. 하지만 게임 퍼블리셔 등의 보안 정책등의 이유로 쿼리 추적기를 쓰지 못하는 경우도 있습니다. 이러한 경우에도 ADO 작동 추적하기 가 대안이 됩니다.
아래의 예는 쿼리 혹은 커맨드시에 지연시간 이상이 되면 이벤트를 받는 예입니다.
class CAdoWrapEvent:public Proud::IDbmsAccessEvent //event 인터페이스를 상속받습니다. { void OnQueryDelayed(LPCWSTR lpszcommand,Proud::CPnTime curtime,uint32_t querytick) { wprintf(L"QueryDelayed - comment:%s curtime:%s delayedtime:%u", lpszcommand,curtime.Format("%x %X").GetString(),querytick); } }; CAdoWrapEvent g_EventSink;//이 인스턴스로 이벤트가 노티 됩니다. void main() { Proud::CDbmsAccessTracker::DelayedAccessThresholdMilisec = 500; //쿼리 지연 감지 시간을 500밀리초로 설정합니다. Proud::CDbmsAccessTracker::SetAdoWrapEvent(&g_EventSink); //이하 ADO wrapper API 를 사용... }
이 기능은 1. ProudNet Database Cache System version 2 내부에서 사용되는 2. ADO Wrapper API
의 이벤트도 받을수 있습니다.
2.7ADO connection pooling 에 대해
Microsoft ActiveX Data Object (ADO)는 자체적으로 connection pooling 을 제공합니다. 이미 특정 데이터베이스에 연결하고 있는 ADO connection 객체가 존재하는 한 동일한 데이터베이스에 연결하려는 ADO connection 객체가 있는 경우 새 연결을 만들지 않고 기존의 연결을 즉시 공용합니다. 따라서 프로그램이 실행되는 동안 데이터베이스에 연결만 해둔 ADO connection 객체를 하나 두고, 매번 로컬 변수로서 ADO connection 객체를 만들어서 쓰고 폐기하는 형식으로 만들어도 좋은 성능을 유지합니다.
아래는 사용 예입니다.
Proud::CAdoConnection g_justConnected; // 데이터베이스에 연결만 유지하는 전역 객체 void main() { CoInitialize(0); g_justConnected.Open(MyDatabaseConnectionString); // 데이터베이스에 연결만 해둡니다. 프로그램이 종료할 때까지 이 연결을 유지만 합니다. ... } void SomeDatabaseAccess() { /* 매번 이렇게 데이터베이스 연결 객체를 만들어도 g_justConnected 가 존재하는 한 기존의 연결을 즉시 재사용합니다. 따라서 성능을 여전히 잘 유지합니다. */ Proud::CAdoConnection conn; conn.Open(MyDatabaseConnectionString); ... }
자세한 것은 Pooling in the Microsoft Data Access Components http://msdn.microsoft.com/en-us/library/ms810829.aspx 에 설명되어 있습니다.
2.8ADO 사용 시 주의점
BSTR Pooling으로 인한 메모리 증가 문제
ADO의 BSTR은 Reference Counting을 하고 Reference Counting값이 0이 되면 자동 해제 됩니다. 하지만 메모리에서 즉시 해제가 되지는 않고 내부적으로 String Pooling을 하게 됩니다. 이 때문에 만약 새로 필요한 문자열을 할당하는 속도가 사용되지 않는 문자열을 해제하는 속도보다 빠를 경우 메모리 사용량이 계속 증가하는 문제가 발생하게 되고 이로 인해서 메모리 부족 현상이 발생할 수 있습니다.
예를 들어 빈번하게 대량의 데이터를 Query해서 RecordSet을 받아오는 경우 데이터의 크기에 비례하여 BSTR이 계속 할당되어 메모리 사용량이 계속 증가하게 됩니다.
위와 같은 문제의 대안으로는 다음과 같이 환경변수를 등록하여 BSTR String Pooling을 사용하지 않도록 하는 방법이 있습니다.
OANOCACHE 1
MSSql의 Stored Procedure을 호출하기 위하여 CAdoCommand.Execute(CAdoRecordset&, long* = 0) 함수를 사용하실 경우 주의하실 점
레코드셋이 닫히거나, 완벽히 fetch될 때까지 output 파라미터나 리턴값을 제대로 사용할 수 없는 문제가 있습니다. 때문에 Stored Procedure 내부에 Select나 Insert 구문이 있다면 output 파라미터나 반환값이 제대로 추출되어지지 않지만, 그러한 구문이 없다면 제대로 추출되어지는 문제를 겪으실 수도 있습니다. 레코드셋을 사용하지 않으신다면 파라미터 지정없이 CAdoCommand.Execute()를 호출해주시면 됩니다.