2008. 9. 19. 23:27

VC++(MFC)에서 ADO와 ADOX를 이용한 MDB 파일 생성 / 연동 / 압축에 관한 모든 것


VC++(MFC)에서 MDB 생성 / 압축 / 연동

 

 

□ 개 요

Access로 생성하는 DB 파일인 MDB 파일의 생성과 압축 및 기본 DB연동(데이터의 추가, 삭제, 검색, 수정)에 대한 내용

○ 다이얼로그 기반의 MFC 프로젝트 상에서 위의 작업들을 수행하는 CDataBase라는 사용자 정의 클래스를 만들고 구현

 

□ 준비 작업

stdafx.h에 다음을 import 한다.

/* DB 사용을 위한 설정*/

#import "C:\Program Files\Common Files\System\ADO\msado15.dll" rename("EOF", "EndOfFile")

 

/* DB 파일 압축을 위한 설정*/

#import "C:\Program Files\Common Files\System\ado\msjro.dll" no_namespace

 

/* DB 파일 생성을 위한 설정*/

#import "c:\Program Files\Common Files\system\ado\msadox.dll"

using namespace ADODB;

using namespace ADOX;

ADO(msado15.dll) 자체에서는 DB 파일 생성과 압축에 대한 라이브러리가 없기 때문에 각각에 필요한 DLL 파일을 Import 해야 한다. 하지만 ADOX(msadox.dll)와 단순히 같이 Import하면 충돌하기 때문에 서로 namespace를 지정해 준다. 또한 인터넷에 돌아다니는 일부 코드는 msadox.dll Import할 때 rename 혹은 no_namespace 옵션을 주곤 하는데 이를 위와 같이 해제한다.

 

○ 초기화 작업을 수행한다.

=> 프로젝트의 메인파일 (TestDlg.cpp) BOOL CTestDlgAPP::InitInstance()에 다음 내용을 추가

/* Ole 컨트롤의 지원을 위한 작업을 가능하게 함*/

AfxEnableControlContainer();

if (!AfxOleInit())

{

    return FALSE;

}

=> 또한 DB를 실제적으로 사용 / 관리하는 CDataBase 클래스의 생성자와 소멸자에 다음 내용을 추가

CDataBase::CDataBase(void)

{

    ...(생략)...

    /* OLE를 초기화하고 DLL을 셋업*/

    ::CoInitialize(NULL);

    ...(생략)...

}

 

CDataBase::~CDataBase(void)

{

    ...(생략)...

    ::CoUninitialize();

    ...(생략)...

}

 

 

 

ADOX를 이용한 MDB 파일 생성 (msadox.dll)

○ 준비 작업

/* Catalog(Database), Table, Column Object에 대한 ADOX Object 포인터 정의*/

ADOX::_CatalogPtr m_pCatalog = NULL;

ADOX::_TablePtr m_pTable = NULL;

ADOX::_ColumnPtr m_pColumn = NULL;

ADOX::_IndexPtr m_pIndex = NULL;

 

/* Object에 대한 Instance 생성*/

m_pCatalog.CreateInstance(__uuidof (Catalog));

m_pTable.CreateInstance(__uuidof (Table));

m_pIndex.CreateInstance(__uuidof(Index));

 

/* Database 생성*/

m_pCatalog->Create((const char *)(CString("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb"));

ADOX::_CatalogPtr 등과 같이 네임스페이스를 정확히 명시해 주지 않으면 충돌 에러

 

Table Column 생성 및 인덱스 설정

/* 테이블 생성(Chart) */

m_pTable->PutName((const char *)strChartTable);

 

/* 필드 생성(Chart Table) */

m_pTable->Columns->Append("r_Code", ADOX::adVarWChar, 10); // 텍스트(3번째 인자는 크기)

m_pTable->Columns->Append("r_Name", ADOX::adVarWChar, 20); // 텍스트

m_pTable->Columns->Append("r_SendDate", ADOX::adVarWChar, 10); // 텍스트

m_pTable->Columns->Append("r_CheckDate", ADOX::adVarWChar, 10); // 텍스트

m_pTable->Columns->Append("r_Contents", ADOX::adLongVarWChar, 0); // 메모(3번째 인자 0)

m_pTable->Columns->Append("r_Number", ADOX::adInteger, 0); // 정수형(3번째 인자 0)

 

/* 인덱스와 기본키 설정(기본키를 지정하고 중복 불가능한 Unique한 인덱스 지정), 인덱스 이름은 마음대로 */

m_pIndex->Name = "CodeIndex";

m_pIndex->Columns->Append("r_Code", ADOX::adVarWChar, 0);

m_pIndex->PutPrimaryKey(-1);// 기본키 지정

m_pIndex->PutUnique(-1);// 중복 불가능

 

/* 설정한 인덱스를 테이블에 붙여넣는다. */

m_pTable->Indexes->Append(_variant_t((IDispatch*)m_pIndex));

 

/* 설정한 테이블을 붙여넣는다. 하나의 테이블 생성 후 제일 마지막에 하는 작업 */

m_pCatalog->Tables->Append(_variant_t((IDispatch*)m_pTable));

 

 

○ 기타 주의 및 특이 사항

=> 중복 가능한 인덱스를 간단히 만들고자 할 경우

/* 중복 가능한 인덱스를 만들때는 밑에 한줄만 추가하면 됨(기본키 지정은 안됨) */

m_pTable->Indexes->Append("CodeIndex", "r_Code");

 

 

=> 여러개의 Table 생성시

/* 첫 번째 테이블 */

m_pTable.CreateInstance(__uuidof (Table));

m_pTable->PutName(...);

m_pTable->Columns->Append(...);

m_pCatalog->Tables->Append(...);

m_pTable = NULL;

 

/* 두 번째 테이블 */

m_pTable.CreateInstance(__uuidof (Table));

m_pTable->PutName(...);

m_pTable->Columns->Append(...);

m_pCatalog->Tables->Append(...);

m_pTable = NULL;

※ 위와 같이 반복적으로 해주면 됨(m_pTable->ParentCatalog = m_pCatalog;)와 같은 코드는 일련 번호 속성을 주는 것이 아닌 필드라면 생략 가능

 

 

=> DB의 필드 속성을 adNumericadDecimal로 하여 생성할 경우

m_pTable->Columns->Append("r_Test", ADOX::adNumeric, 10);

 

/* adNumeric, adDecimal일 경우,정밀도(Precision), 배율(NumericScale) 설정 필수, 안하면 런타임에러, 설정을 위해 adNumeric 필드의 컬럼을 얻어와 필요한 설정 작업 수행 */

m_pColumn = m_pTable->Columns->GetItem("r_Test");

m_pColumn->Precision = 8;

m_pColumn->NumericScale = 4;

 

 

=> NULL을 사용하는 설정

/* Null을허용하는설정*/

m_pColumn = m_pTable->Columns->GetItem("r_Test");

m_pColumn->Attributes = adColNullable;

 

 

=> 자동 증가(Auto Increment) 속성을 가진 정수형 필드 생성

/* "r_Number"필드의 일렬번호(Auto Increment) 속성을 주기위해 해줘야 함, 안하면 런타임 에러 */

m_pTable->ParentCatalog = m_pCatalog;

 

/* Auto Increment 속성으로 설정, 즉 값이 자동 증가함. TRUE로 하면 런타임 에러, true로 해야 함 */

m_pTable->Columns->GetItem("r_Number")->Properties->GetItem("AutoIncrement")->Value = true;

 

/* long으로 캐스팅 해줘야 함, 안하면 런타임 에러*/

_variant_t varValue1( (long) 1 );

m_pTable->Columns->GetItem("r_Number")->Properties->GetItem("Seed")->Value = varValue1;

m_pTable->Columns->GetItem("r_Number")->Properties->GetItem("Increment")->Value = varValue1;

  "AutoIncrement", "Seed", "Increment" 문자열은 이미 내부적으로 속성이름으로서 정의되어 있음

 

 

 

 

MDB 파일 압축 (msjro.dll)

MDB 파일은 계속적인 DB의 추가, 삭제, 업데이트 등의 작업에 따라 용량이 계속 커지게 됨으로 압축을 해주는 것이 좋음

IJetEnginePtr jet(__uuidof(JetEngine));

 

/* mdb 파일압축*/

jet->CompactDatabase( "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=data.mdb;","Provider=Microsoft.Jet.OLEDB.4.0;Data Source=__tmp__.mdb; Jet OLEDB:Engine Type=5;");

 

/* 압축된Temp 파일을원본파일로바꿈*/

DeleteFile("data.mdb");

MoveFile("__tmp__.mdb", "data.mdb");

※ 해당 MDB 파일에 연결된 Connection이 없어야 합니다.

※ 원래 DB 파일인 data.mdb를 압축하는데 중간에 __tmp__.mdb라는 임시파일로 생성하여 압축,따라서 다시 압축된 임시파일을 원래의 DB파일로 바꿔야함.

 

 

 

 

MDB 연동 (msado15.dll)

DB 연결

HRESULT hr = S_OK;

ADODB::_ConnectionPtr m_pConnection;

 

/* 연결 인스턴스 생성*/

hr = m_pConnection.CreateInstance(__uuidof(Connection));

 

if(SUCCEEDED(hr))

{

    /* DB 연결*/

    m_pConnection->ConnectionString = _bstr_t("Provider=Microsoft.Jet.OLEDB.4.0; Data Source=data.mdb;");

    m_pConnection->Open("", "", "", adModeUnknown);

}

ADODB::_ConnectionPtr 클래스는 보통 사용하는 클래스의 멤버 변수로서 전역으로 선언한다., 여기서는 CDataBase 클래스의 멤버변수로 선언하여 사용하나 설명의 편의상 위와 같이 지역변수 같이 선언하였다.

 

 

DB 연결 해제

m_pConnection->Close();

m_pConnection = NULL;

 

 

DB에 레코드 추가

CString query;// 쿼리문이 저장될 변수

 

/* 쿼리*/

query = "INSERT INTO Chart (r_Code, r_Name) values ('0123456', '홍길동')";

 

_bstr_t executeQuery = query;

 

/* 트랜잭션 시작*/

m_pConnection->BeginTrans();

 

/* "추가" 쿼리 실행*/

m_pConnection->Execute(executeQuery, NULL, adCmdText);

 

/* 트랜잭션 종료*/

m_pConnection->CommitTrans();

※ 예외 처리를 추가하여 Commit에 실패하면 m_pConnection->RollbackTrans(); 코드를 통해 롤백하도록 한다.

 

 

DB의 레코드 삭제

CString query;// 쿼리문이 저장될 변수

 

/* 쿼리*/

query = "DELETE FROM Chart WHERE r_Code='0123456'";

 

_bstr_t executeQuery = query;

 

/* 트랜잭션시작*/

m_pConnection->BeginTrans();

 

/* "삭제" 쿼리실행*/

m_pConnection->Execute(executeQuery, NULL, adCmdText);

 

/* 트랜잭션종료*/

m_pConnection->CommitTrans();

※ 예외 처리를 추가하여 Commit에 실패하면 m_pConnection->RollbackTrans(); 코드를 통해 롤백하도록 한다.

 

 

DB의 레코드 수정

CString query;// 쿼리문이 저장될 변수

 

/* 쿼리*/

query = "UPDATE Chart SET r_Name='홍길동' WHERE r_Code='0123456'";

 

_bstr_t executeQuery = query;

 

/* 트랜잭션시작*/

m_pConnection->BeginTrans();

 

/* "추가" 쿼리실행*/

m_pConnection->Execute(executeQuery, NULL, adCmdText);

 

/* 트랜잭션종료*/

m_pConnection->CommitTrans();

※ 예외 처리를 추가하여 Commit에 실패하면 m_pConnection->RollbackTrans(); 코드를 통해 롤백하도록 한다.

 

 

DB의 모든 레코드 검색

int count = 0;

ADODB::_RecordsetPtr record;// DB의 레코드셋

 

_bstr_t bKey;

_bstr_t bKeyContents;

 

CString query;// 쿼리문이 저장될 변수

CString m_Index[MAXCOUNT];

CString m_Contents[MAXCOUNT];

 

/* 쿼리*/

query = "SELECT * FROM MacroData";

 

_bstr_t executeQuery = query;

 

/* 쿼리실행*/

record = m_pConnection->Execute(executeQuery, NULL, adCmdText);

 

while(!record->EndOfFile)

{

    /* 레코드의 각 필드에서 해당정보를 가져옴*/

    bKey = record->Fields->GetItem("r_Key")->Value;

    bKeyContents = record->Fields->GetItem("r_KeyContents")->Value;

 

    m_Index[count].Format("%s", (LPCSTR)bKey);

    m_Contents[count].Format("%s", (LPCSTR)bKeyContents);

 

    /* 다음레코드로진행*/

    record->MoveNext();

    count++;

}

 

/* 레코드셋 해제*/

record->Close();

record = NULL;

msado15.dll Import하여 ADO만 사용하는 경우에는 while(!record->EndOfFile) 로 하면 컴파일 에러 발생, while(!pRecordSet->GetadoEOF())을 사용하도록 한다.

 

 

DB의 특정 레코드 검색

ADODB::_RecordsetPtr record;// DB의 레코드셋

 

_bstr_t bCode;

_bstr_t bName;

 

CString query;// 쿼리문이 저장될 변수

CString m_Code;

CString m_Name;

 

/* 쿼리*/

query = "SELECT * FROM Chart WHERE r_Code='0123456'";

 

_bstr_t executeQuery = query;

 

/* 쿼리 실행*/

record = m_pConnection->Execute(executeQuery, NULL, adCmdText);

 

/* 레코드의 각 필드에서 해당 정보를 가져옴 */

bCode = record->Fields->GetItem("r_Code")->Value;

bName = record->Fields->GetItem("r_Name")->Value;

 

/* 해당 정보를 입력 */

m_Code.Format("%s", (LPCSTR)bCode);

m_Name.Format("%s", (LPCSTR)bName);

 

/* 레코드셋 해제 */

record->Close();

record = NULL;

 

 

=> 레코드의 필드 값이 정수형일 때

int count = record->GetCollect("macroCount").intVal;

 

 

DB 작업 수행 간에는 try, catch로 예외처리를 해주는 것이 좋다.예외는 _com_error& 형태 이므로 catch(_com_error& e)로 하면 되고 e.Error()로 에러 코드 번호를 알 수 있다.

 

DB의 필드 타입 종류와 프로그래밍 코드와의 맵핑 정보는 MSDN을 참고

 

 

 

UDL 파일 사용

xxx.udl 파일을 작성

=> udl 파일을 만드는 방법은 메모장 같은 텍스트 에디터 파일을 비운상태에서, "xxx.udl" 형태로 파일명을 지정한 후, 저장

 

○ 설 정

=> "xxx.udl"를 선택 후, 우측버튼을 클릭해서 속성메뉴를 선택

 

 

=> 공급자 탭에서, ADO에서 사용하는 공급자를 선택

※ 여기에서는 mdb 파일을 사용하므로, "Microsoft Jet 3.51 OLE DB Provider", "Microsoft Jet 4.0 OLE DB Provider" 등을 선택

 

=> 연결 탭에서 데이터베이스명을 입력

※ 옆의 파일 찾기 버튼을 클릭하여 사용할 mdb 파일을 선택

 

○ 연결 테스트

=> 연결 테스트 버튼을 눌러 테스트를 실행

 

udl 파일 사용시 DB 연결

HRESULT hr = S_OK;

_ConnectionPtr m_pConnection;

 

/* 연결 인스턴스 생성*/

hr = m_pConnection.CreateInstance(__uuidof(Connection));

 

if(SUCCEEDED(hr))

{

    /* DB 연결*/

    hr = m_pConnection->Open(_bstr_t(L"File Name=data.udl;"), _bstr_t(L""), _bstr_t(L""), adModeUnknown);

 

    if(SUCCEEDED(hr))

    {

        // 연결 성공시 수행작업

    }

}

출처 : [직접 서술] 블로그 집필 - punchdrunk7님의 블로그

 

출처 : Naver 지식인( http://kin.naver.com/knowhow/entry.php?d1id=5&dir_id=5&eid=T0qaSfmxohdD2r6mS88QItjLZuXfNGNe&qb=bWRiIMbEwM8gu/28ug==&pid=fLM1sdoi5UKssvLC6dosss--410956&sid=SNO1AUxq00gAADNkDOI )