반응형
[MFC로 구현하는 DB 프로그래밍] ② DB의 다리 ODBC

김익중 (네오위즈)   2004/10/22
연재순서
1회. 로컬 전용 제트 엔진 DAO
2회. DB는 달라도 다리는 하나 ODBC
3회. 모든 것은 OLE로 'OLE-DB' <끝>
엑세스와 DAO를 통해 로컬의 DB에 접근하는 방법을 지난 글에서 다뤘습니다. 하지만 거의 대부분 DBMS와 데이터베이스 접근 클라이언트는 물리적으로 떨어져 있는 경우가 대부분입니다. 떨어진 DBMS에 어떻게 접근할 수 있을까요? 그것을 위해 ODBC라는 다리(브릿지, bridge)가 존재합니다. ODBC는 데이터베이스 프로그래밍 역사에서 아주 중요한 역할을 합니다.

DAO를 맛보기 위해 CDaoRecordset 클래스를 이용한 간단한 일기장을 구현해 보았습니다. 사실 DAO에는 일기장 예제에서 다루지 못한 다양한 기능들이 들어 있지만 지면의 한계와 현실적인 측면을 바라본다면 ODBC나 OLE-DB에서 못다한 기능들을 살펴보는 것이 좋을 것입니다. 이번 호에서는 CRecordset이라는 클래스가 맹활약을 할 것입니다. 먼저 ODBC에 대해 알아보기로 하죠. ODBC는 90년대 중후반에 나타난 꽤나 오래된 기술이지만 데이터베이스 프로그래밍에 있어서 획기적인 개념이었습니다.

DB에 대한 짧은 역사
암호 해독, 탄착점 계산, 세금 계산 등 이러한 것들은 컴퓨터가 발명되기 위한 이유였습니다. 그리고 컴퓨터는 두 가지 놀라운 능력이 있지요. 하나는 단순 반복 작업을 엄청나게 빠르게 수행한다는 점이며 다른 한 가지는 전기가 끊기지 않은 한 잊어버리지 않고 계속 기억한다는 점입니다(역시 컴퓨터의 본질은 함수와 변수로 나눠지네요). 이렇게 잊어버리지 않고 계속 기억할 수 있다는 사실은 외부 기억장치의 발전과 함께 ‘대용량 기억 기계’로 재탄생합니다. 바로 데이터베이스의 탄생이죠.

하지만 불과 몇 십 년 전만 해도 컴퓨터(PC가 아닙니다)는 아무나 만질 수 있는 물건이 아니었습니다. 1960년대 말에 이르러 처음으로 ‘데이터베이스’란 용어가 ‘한 조직의 응용 시스템들이 공용(shared)하기 위해 통합(integrated), 저장(stored)한 운영(operational) 데이터의 집합’이란 개념으로 정의되고, 곧 이어 데이타베이스를 관리하기 위한 시스템인 DBMS가 나타나게 됩니다.

상용 관계형 DBMS, 오라클의 탄생
1970년대에 이르러 컴퓨터는 좀 더 대중화되었습니다. DB 역사에서 70년대가 중요한 이유는 1978년에 첫 번째 관계형 데이터베이스 제품인 오라클 1이 출현했기 때문입니다. 그 이전에는 1960년대에 개발된 IBM과 인텔의 계층형 데이터베이스가 주류였습니다. ‘오라클(Oracle)’은 고대의 신의 신탁을 받는 신관를 의미하는데 정말 신의 계시를 받은 것처럼 대단히 높은 인기를 끌었습니다. 여기서 중요한 점은 오라클은 최초의 상용 관계형 DBMS란 점입니다.

한마디로 현대적 의미의 DBMS(DataBase Management System)을 보통 사람들이 만져볼 기회는 오라클이 처음이었습니다. 1970~80년대에 사설 DBMS로 기업 전산망을 갖추기가 어려웠습니다. 바로 이점에서 오늘날까지도 오라클이 높은 인지도를 가지는 이유입니다. 친구들의 전화번호조차 다이어리나 수첩에 몇 명 적기 시작하면 도저히 다른 수첩으로 옮겨 적을 수 없습니다. 옮기기 귀찮고 옮기다가 사고로 데이터를 유실할 수도 있으니까요. 고작 친구들의 전화번호를 다른 수첩에 옮기기도 힘든데 기업의 몇 십년 된 정보들을 어떻게 다른 곳으로 마이그레이션(migration)할 수 있을까요.

바로 이점에서 하나의 벤더가 공급하는 DBMS의 무서움을 느낄 수 있습니다. 초창기의 특정 DBMS로 결정한다면 추후 다른 DBMS로의 마이그레이션은 사실상 불가능에 가까울 정도입니다. 그리고 초창기 비즈니스 로직에 맞춰놓은 DB 설계를 새로이 튜닝한다든가 변경한다는 것도 너무나 힘듭니다. 방금 ‘불가능에 가깝다’, ‘너무나 힘들다’고 이야기했죠? 그런 작업들을 위해 수많은 기술과 툴들이 발전되어 있으며 DBA를 꿈꾸는 개발자라면 눈여겨 볼만한 분야입니다.

DBMS의 개인화 dBase Ⅲ+, Fox Pro
오라클로 인해 사용자는 터미널을 이용해 오라클이 설치된 유닉스 호스트를 이용했습니다(70년대라면 아직 PC가 나오지 않았던 시기입니다). 그러나 차츰 호스트/터미널 방식에서 개인 사용자를 위한 PC 환경으로 변해갑니다. PC의 출현 역시 DB 역사에서 중요한 의미를 가집니다. 애플이나 MSX와 같은 8비트 PC(‘Personal Computer’의 약자이지만 IBM의 상품명이기도 합니다)에도 개인 사용자를 위한 DBMS가 있습니다. 하지만 메모리와 CPU의 한계로 베이직으로 만든 ‘주소록’에 불과했습니다.

그러나 IBM-PC/XT는 무려 640KB나 되는 광대한 주메모리를 갖고 있었으며 초강력 8088 CPU는 무려 4.77 MHz(기가 헤르쯔가 아닙니다)나 되는 놀라운 속력을 갖고 있었습니다. 정말로 XT, 그리고 현재 PC의 전신은 개인용 DBMS와 스프레트 시트만을 위해 나온 업무 전용기입니다. 오죽하면 그래픽 모드란 것이 사실은 불가능했을 정도였으니까요.

하지만 XT는 개인 업무를 확실하게 맡겨둘 수 있는 꿈의 개인용 컴퓨터였고, DBMS도 개인용 컴퓨터에 걸맞는  DBMS로 새롭게 나타납니다. 이 시대에 가장 사랑받았던 DBMS는 바로 dBase 시리즈와 Fox Pro입니다. 지금의 관점으로 본다면 원시적인 시스템이지만 대형 유닉스 호스트에서만 가능했던 DBMS를 개인이 가질 수 있게 된 것입니다.

그러나 이때에는 치명적인 문제가 있는데 DBMS와 프로그램이 분리되지 않았다는 사실입니다. 즉 dBase나 Fox Pro같은 DBMS는 DB이면서 동시에 자체 프로그래밍 언어를 가지고 있었습니다. 해당 DBMS로 만든 DB를 이용하기 위해서는 반드시 DBMS 본체까지 있어야만 했습니다. 하지만 그러한 약점 덕분에 추후에 나온 클리퍼(Clipper)라는 프로그램이 인기를 얻었습니다. dBase나 Fox Pro로 만든 DB를 이용해 EXE 파일, 즉 독립형 프로그램으로 컴파일해주는 역할을 했습니다. 그러나 클리퍼 역시 현재와 같은 DBMS와 애플리케이션간의 서버/클라이언트 개념은 아닙니다. 그렇다 하더라도 이 시절을 이끌었던 프로그램들은 현재와 같은 환경을 위한 기반을 다져주었습니다. 윈도우 시스템 폴더에 있는 moricon.dll이란 리소스 DLL을 살펴보면 아직도 그 때의 프로그램들을 위한 아이콘들이 21세기에도 고스란히 존재하고 있음을 알 수 있습니다.

90년대 비즈니스의 중심, DBMS
DOS에서 윈도우로의 발전, 그리고 TCP/IP를 중심으로 한 C/S의 발전, 더 막강해진 하드웨어와 눈부시게 성장한 DBMS는 모든 프로젝트의 중심에 DBMS를 두게 되었습니다. 오라클과 MS의 SQL 서버는 놀랍도록 발전해 종래의 프로그램이 맡던 자료구조까지도 DBMS가 맡게 되었습니다. 이제부터 여기서 중심이 되는 데이터베이스 접속 개념이 생겨납니다.  

만약 A라는 DBMS를 이용하여 애플리케이션을 만들었다고 가정합시다. A라는 DBMS에 접속하기 위해서는 당연히 A를 만든 벤더에서 제공하는 방법을 준수해야 합니다. 그런데 어떤 이유로 프로그램의 다음 버전에서 B라는 DBMS를 이용하게 되었습니다. 분명 DBMS만 바꾸는 것이지만 애플리케이션도 상당 부분 뜯어고쳐야 합니다. A에 접속하기 위한 루틴들을 모조리 B에 접속하기 위한 루틴으로 바꾸어야 하죠. 그런 불합리한 면을 위해 MS는 UDA(Universal Data Access)라는 방법을 개발했습니다.

UDA는 DBMS 종류에 독립적으로 애플리케이션을 만들 수 있게 한 방법이며 ADO(ActiveX Database Object), OLE-DB, ODBC(Open Database Connectivity) 등이 대표적인 방법입니다. UDA 모델을 이용하면 개발자는 해당 DBMS를 직접 조작하는 것이 아닌 UDA 계층에서 제공해주는 메쏘드로 특정 DBMS에 관계없이 작업할 수 있습니다. 이 얼마나 멋진 개념입니까? UDA 덕분에 DBMS를 바꾸더라도 애플리케이션은 코드를 수정할 일이 사라진 것입니다. 우린 UDA 중에서 VC++에서 제공해주는 ODBC와 OLE-DB를 이용해볼까 합니다.

또한 기억할 것은 UDA는 어디까지나 2-티어 방식입니다. 즉 ODBC나 OLE-DB가 한 계층이 아닌 애플리케이션과 DBMS 사이의 다리(Bridge) 역할만 하는 것입니다. MTS(Microsoft Transaction Server, 구성 요소 서비스)를 이용하면 3-티어 방식으로 애플리케이션과 DBMS 사이에 미들웨어를 둘 수 있습니다(이것이 바로 DNA 2000입니다). 이는 성능과 안정성을 위한 방법으로 MS 계열로 나가기 위한 개발자라면 MTS를 이용한 프로젝트를 해보는 것이 좋습니다. 아쉽게도 이 부분은 MFC를 이용한 이 강좌의 수준을 넘는 부분이므로 앞으로 공부해야 할 부분으로 남겨둡니다. 가끔 ODBC나 JDBC(자바)를 MTS와 같은 하나의 계층으로 착각하는 독자도 있는데 ODBC, JDBC는 연결 브리지일 뿐이며 절대 하나의 계층은 아닙니다.

<그림 1> ODBC의 역할

모두 쉬고 있을 일요일 오후, 개발실에 혼자 켜진 PC가 있었으니 바로 레아군의 자리였습니다. 유나의 코드에서 슬쩍 훔쳐(?) 적어 둔 SQL 서버의 IP와 로그인 계정을 손에 넣은 레아군은 지난 달 만들었던 일기장 프로그램의 DBMS를 SQL 서버로 옮기고 있었습니다. ODBC 브릿지 사용법을 게시판에서 배운 레아군이 IP와 계정을 안 이상 거칠 것이 없습니다. 참고로 이런 행위는 절대 따라하면 안됩니다.

MS SQL 서버로의 마이그레이션
원래 DBMS에 관한 일은 애플리케이션 개발자가 아닌 DBA나 DBMS를 맞은 팀에서 건드려야 할 부분이겠지만 개인 프로젝트이므로 함께 따라가 봅시다. 이번 시간부터는 엑세스 MDB가 아닌 MS SQL 서버 2000을 이용하겠습니다. SQL 서버는 무척 강력한 DBMS로 윈도우 서버를 사용하는 서비스라면 십중팔구 SQL 서버를 이용합니다. SQL 서버만으로도 너무 공부할 것이 많지만 MFC 개발자로 알아둘만한 것만 살펴보겠습니다. 지난 시간과 마찬가지로 데이터베이스를 사용하기 위해 테이블을 만들어야 합니다. 이미 MS SQL 서버에 익숙한 독자라면 지난 시간 만들었던 테이블을 똑같이 만들어도 될 것입니다. 하지만 간단한 마이그레이션을 직접해보는 것도 좋겠죠? ODBC 연결을 만들어 MDB에서 MS SQL 서버로 자동적으로 옮기겠습니다.

ODBC 브릿지 만들기
ODBC를 사용하기 위해서는 ODBC에서 새로운 연결을 만들어야 합니다. 윈도우 9X 계열에서 프로그래밍하는 독자라면 제어판을, 윈도우 2000 이상에서 프로그래밍을 한다면 「제어판 | 관리도구」로 가봅니다. 관리도구 안에는 ‘데이터 원본(ODBC)’이라는 아이콘이 있는데 이것을 통해 ODBC 연결을 만들 수 있습니다. 참고로 관리 도구 아이콘 중 가장 앞에 있는 ‘구성 요소 서비스’가 앞서 이야기한 MTS입니다.

ODBC를 더블클릭한 후 시스템 DSN 탭을 눌러 ‘추가’ 버튼을 누릅니다. 다양한 종류의 데이터베이스 드라이버들이 출력될 것입니다. 먼저 MDB에서 MS SQL 서버로 가져오기 위해 MDB 드라이버를 이용하는 연결을 만들어야 합니다. 중간에 있는 ‘Microsoft Access Driver( *.mdb)’를 선택합니다. 세 번째 윈도우에서 ‘데이터 원본 이름’을 묻습니다. 연결의 이름을 짓는 것이죠. 아무 이름이나 적습니다. 필자는 ‘MDB_Diary’라고 지었습니다. 그 다음 ‘선택’에서 지난 시간 만들었던 mdb 파일을 선택합니다. 이것으로 ODBC 연결을 성공적으로 만들었습니다.

<화면 1> ODBC 연결을 만들었습니다.

이것이 ODBC 연결을 만드는 방법입니다. 무척 간단하지요? 여기에서 중요한 것은 사용하고자 하는 DBMS에 맞는 드라이버를 선택해야 한다는 사실입니다. 또한 자신에게 없는 데이터베이스 드라이버라면 해당 제작사의 홈페이지에 가면 ODBC용 드라이버를 다운받아 설치할 수 있습니다. 그리고 ODBC 연결이 만들어진 이상, 어떤 프로그램이라도 ODBC를 지원한다면 일기장을 위해 만든 mdb를 가져올 수 있습니다.

MDB를 MS SQL 서버로 옮기자
MS SQL 서버를 사용해보지 못한 개발자를 위해서 차근차근 따라해 보겠습니다. MS SQL 서버는 다양한 관리툴을 갖고 있는데 초보 개발자라면 ‘엔터프라이즈 매니저’를 이용해서 마우스 클릭만으로 상당 부분을 손쉽게 사용할   있습니다(하지만 쿼리 분석기에 빨리 친해지기를 권장합니다). 엔터프라이즈 매니저에서 ‘SQL 서버 그룹’으로 가면 기본적으로 자신의 PC 이름이 나오며 7개의 폴더가 나옵니다. 이중 ‘데이터베이스’ 폴더는 실제 데이터베이스가 담긴 폴더입니다.

데이터베이스 폴더에서 마우스 오른쪽 버튼을 눌러 모든 「모든 작업 | 데이터 가져오기」를 선택합니다. 바로 방금 만든 MDB_Diary에서 데이터를 가져오기 위해 이 메뉴를 선택한 것입니다. 이제 ‘데이터 가져오기 마법사’가 작동할 것입니다.

첫 번째 화면은 가져올 데이터를 선택하는 부분입니다. 최강의 DBMS답게 mdb를 그대로 읽을 수도 있지만 방금 만든 ODBC 브릿지를 이용해 가져오도록 하겠습니다. 이중에서 어떤 부분을 건드려야 할까요? ODBC라고 적힌 항목을 선택해야겠죠? ‘데이터 원본’에서 ‘Other(ODBC Data Source)’를 선택해줍니다. 메뉴 모양이 변경되며 ‘사용자/시스템 DSN’이 나타납니다. 방금 만든 연결 이름인 ‘MDB_Diary’를 선택합니다. 이름과 암호는 걸어두지 않았으니 무시하지요. 현재 보는 윈도우가 <화면 2>입니다.

이제 MS SQL 서버에 담길 데이터베이스를 만들어줘야겠죠? 다음 버튼을 눌러 대상 선택 화면으로 넘어갑니다. 서버는 바로 자신의 PC를 선택하며 자신의 MS SQL 서버의 암호와 비밀번호를 알맞게 적어줍니다. 마지막 ‘데이터 베이스’ 항목에서는 ‘새로 만들기’를 선택해 새 데이터베이스를 만듭니다. 새로 만들기 버튼을 누르면 새로운 데이터베이스의 이름을 묻는데 필자는 Diary라고 적어줬습니다. 이 화면은 <화면 3>이 될 것입니다.

<화면 2> 데이터를 가져오기 위한 원본 선택

<화면 3> 데이터를 담을 데이터베이스를 만듭니다.

이제 모든 과정의 끝입니다. ‘다음’ 버튼을 계속 눌러 작업을 끝냅니다. ‘1개의 ACCESS 테이블을 Microsoft SQL 서버(으)로 복사했습니다.’라는 메시지가 나오면 성공적으로 마이그레이션한 것입니다. 새로 만들어진 Diary에 테이블 항목으로 가서 ‘Diary’ 테이블로 가보면 엑세스에서 작업한 내용이 그대로 옮겨져 있음을 확인할 수 있습니다. 눈치 빠른 독자분이라면 이제까지 살펴본 MS SQL 서버의 메뉴들에서 mdb뿐만 아니라 다양한 데이터와 텍스트들을 MS SQL 서버로 옮길 수 있다는 것을 눈치챘을 것입니다.

더 중요한 것은 이것은 단지 MS SQL 서버만의 독립된 기능이 아니란 점입니다. ODBC나 OLE-DB 같은 다양한 UDA를 이용해 우리 스스로도 이러한 마이그레이션 프로그램을 손쉽게 만들 수 있습니다. 실제 현업 환경에서 DBMS는 데이터 저장소뿐만 아니라 데이터 변환기로도 이용되며 그러한 툴을 직접 만든다면 상당히 유용하게 사용될 것입니다. 예컨대 이메일을 데이터베이스로 자동으로 옮긴다든가 엑셀 문서를 데이터베이스로 옮기는 것, 혹은 HTML 기반의 게시판을 데이터베이스로 다시 옮기는 것은 상상만으로도 유용하다고 느껴지지 않습니까?

<표 1> SQL 서버의 Diary 테이블

이번 시간에는 ODBC 연결을 이용한다고 하였으니 지금 만든 테이블 역시 ODBC 연결을 만들어야겠죠. 또 다시 관리 도구로 찾아가 ODBC 연결을 만듭니다. MS SQL은 ODBC 드라이버 중 가장 아래에 놓여있습니다. SQL 서버를 선택하고 다음 버튼을 누릅니다. 드라이버가 다른 만큼 화면도 다릅니다. <화면 5>는 SQL 서버로 설정했을 시의 ODBC 화면입니다. 이중 관심있게 봐야 하는 곳은 마지막 ‘서버’라는 항목입니다. 비록 필자의 환경은 SQL 서버와 VC++가 같은 곳에 위치하고 있지만 ODBC를 이용한다면 TCP/IP를 이용할 수 있으므로 SQL 서버의 위치는 무관합니다. 여기에 컴퓨터 이름 혹은 해당 위치의 IP 주소를 적으면 물리적으로 떨어진 곳의 SQL 서버라도 찾아갑니다. SQL 서버에 등록된 아이디와 비밀번호, 그리고 이용한 데이터베이스 이름인 Diary를 선택하고 연결 등록을 마칩니다. 모든 과정이 성공적으로 마쳤으면 데이터베이스 연결 테스트도 해보세요. 모든 것이 성공이면 일기장 프로그램을 성공적으로 mdb에서 SQL 서버로 마이그레이션한 것입니다.

만약 SQL 서버를 처음으로 경험한 독자라면 모든 것이 낯설 수도 있겠습니다만 데이터베이스는 MFC 개발자의 기본 스킬 중 하나이므로 반드시 자신의 것으로 만들어야만 합니다. 이제까지의 짧은 설명은 기본 능력일 뿐이지 특별한 개발자 혹은 DBA만의 스킬이 절대 아닙니다. SQL 서버에 대해 보다 많은 것을 알고 싶은 독자라면 ‘거북엄마의 SQL 서버 연구실(http://www.mssqllaboratory.pe.kr/)’을 강력히 추천합니다. Q&A 형식으로 만들어진 이곳은 데이터베이스 개발자가 겪는 실무 중심의 문제 상황과 그 해결법들이 잘 나와 있습니다(다만 너무 기초적인 SQL문 질문은 사양합니다).

“ODBC를 이용하여 성공적으로 억세스에서 SQL 서버로 데이터를 옮겼어. 이젠 어떤 자리에서나 일기장 프로그램이 작동되겠지?“ 일기장 프로그램과 ODBC 브릿지를 디스켓에 옮겨 담은 레아군은 자리를 옮겨가며 일기장의 실행 결과를 확인하고 있었습니다.

일기장 프로그램도 ODBC로
CRecordet을 이용한 데이터베이스 접속
이제 일기장 프로그램도 고쳐야겠죠? 지난 시간 사용했던 예제를 그대로 이용하여 DAO에서 ODBC 기반으로 옮기겠습니다. 프로젝트를 그대로 복사해 와서 ODBC를 추가할 것입니다. 먼저 CMyDaoDB를 사용하는 부분은 주석처리해 주세요. 그렇다 하더라도 거의 대부분의 소스는 기존과 똑같습니다. ODBC를 사용하기 위해서는 CDaoRecordset 대신 CRecordset, CDaoDatabase 대신 CDatabase 클래스를 이용한다는 점만 다릅니다. 가장 먼저 <화면 6>과 같이 클래스 위저드에서 CRecordset 클래스에서 상속받은 CMyOdbcDB 클래스를 만듭니다.

CMyOdbcDB 클래스의 변수인 m_MyOdbcDB 를 추가한 후 기존 소스 DaoDiaryDlg.cpp에서 m_MyDaoDB 변수를 이용한 부분을 전부 m_MyOdbcDB로 고칩니다. 이럴 경우, 툴 메뉴의 Edit에 있는 Replace를 이용한다면 아주 쉽겠죠? 그리고 stdafx.h에 #include 를 추가해줍니다. CDaoRecordset를 이용할 경우 를, CRecordset를 이용할 경우에는 를 추가해줘야 합니다. 컴파일 이전에 CMyOdbcDB 클래스를 잠시 살펴봅시다.

<화면 6> ODBC를 이용하는 CRecordset 클래스를 추가합니다.

 <리스트 1> MyOdbcDB.cpp> ODBC를 연결하는 부분

CMyOdbcDB::CMyOdbcDB(CDatabase* pdb)
   : CRecordset(pdb)
{
   //{{AFX_FIELD_INIT(CMyOdbcDB)
   m_ID = 0;
   m_Weather = 0;
   m_Title = _T("");
   m_Contents = _T("");
   m_nFields = 5;
   //}}AFX_FIELD_INIT
   m_nDefaultType = snapshot;
}

CString CMyOdbcDB::GetDefaultConnect()
{
   // DB 로그인을 묻지 않도록 ID와 PWD를 설정해줍니다.
   return _T("ODBC;DSN=Diary;UID=sa;PWD=******");
}

CString CMyOdbcDB::GetDefaultSQL()
{
   return _T("[dbo].[Diary]");
}

void CMyOdbcDB::DoFieldExchange(CFieldExchange* pFX)
{
   //{{AFX_FIELD_MAP(CMyOdbcDB)
   pFX->SetFieldType(CFieldExchange::outputColumn);
   RFX_Long(pFX, _T("[ID]"), m_ID);
   RFX_Date(pFX, _T("[Date]"), m_Date);
   RFX_Long(pFX, _T("[Weather]"), m_Weather);
   RFX_Text(pFX, _T("[Title]"), m_Title);
   RFX_Text(pFX, _T("[Contents]"), m_Contents);
   //}}AFX_FIELD_MAP
}

<리스트 1>은 CDaoRecordset과 큰 차이가 없지만 GetDefaultConnect() 부분이 조금 달라졌습니다. DAO였을 때는 로컬에 담긴 mdb 파일의 경로만 설정해줬습니다. ODBC일 경우에는 데이터베이스의 물리 위치는 관계없습니다. 따라서 ODBC를 사용할 것이라고 ODBC라고 적어줬으며 연결 이름(DSN)은 Diary라고 설정되어 있습니다. 여기까지는 MFC가 만들어주는 부분이나 SQL 서버에 로그인하기 위한 ID와 비밀번호는 적혀있지 않습니다(보안을 위해 그런 것이겠죠?) 만약 이 부분에 ID와 비밀번호가 없다면 프로그램 동작시 항상 로그인을 해야 합니다. 따라서 <리스트 1>과 같이 추가해 자동으로 로그인이 되도록 합니다. 나머지 부분들은 DAO 때와 똑같습니다. 자동으로 멤버 변수가 만들어졌으며 DoFieldExchange에서 데이터 교환도 일어납니다.

여기서 반드시 바꿔줘야 할 부분이 있습니다. 바로 날짜 형식 문제입니다. m_Date는 CTime 자료형으로 바뀌었습니다. DAO에서는 COleDateTime였습니다. 반드시 CTime을 COleDateTime으로 바꿔주길 바랍니다. 이는 날짜를 입력할 때 편리하게 코딩하기 위해서입니다. MS SQL 서버를 이용할 경우 datetime 자료형에서 곤란을 겪는 독자들이 많은데 주로 CRecordset에서 기본적으로 제공해 주는 CTime 자료형을 그대로 쓰기 때문입니다. 문자열 기반의 날짜/시간을 CTime으로 변경하기 위해서는 꽤나 고생해야 하는데 처음부터 COleDateTime을 이용하면 지난 시간처럼 편리하게 SetDate()를 사용할 수 있습니다. 따라서 m_Date를 MFC가 지정해준 CTime 대신 COleDateTime으로 고치길 바랍니다. 이외에도 날짜/시간이 주요하게 사용되는 프로그램이라면 COleDateTime을 쓰기를 권장합니다.

CMyOdbcDB는 CMyDaoDB와 상당히 흡사합니다. 하지만 이것으로 DAO와 ODBC가 비슷한 기술이라고 착각해서는 안됩니다. 이 둘은 전혀 다른 기반의 기술입니다. 하지만 MFC에서는 개발자를 위해 둘 중 하나만 알더라도 손쉽게 옮길 수 있도록 같은 함수를 제공하는 것입니다. 그럼 지난 시간에 다루지 못한 검색과 수정을 추가해보겠습니다.

일기를 수정하자, UPDATE
데이터베이스, 아니 SQL 문에서 수정에 해당하는 문장은 UPDATE입니다. UPDATE는 필드 값 이외에도 테이블 구조까지 바꿀 수 있는 강력한 문장입니다. 주로 필드의 값을 변경하기 위한 UPDATE 문장은 다음과 같습니다.

UPDATE [테이블명] SET [필드명] = '새 데이터' WHERE [필드명] = 'CA'

이 의미는 테이블 내에서 데이터가 CA인 필드를 새 데이터로 바꾼다는 의미입니다. 이런 SQL 문을 직접 전달할 수도 있지만 그럴 경우 데이터베이스에 무관하게 사용한다는 CRecordset의 의미가 약해집니다. 따라서 CRecordset에서 제공해주는 수정 함수를 살펴보죠. 먼저 ‘수정’이라는 버튼을 새롭게 만들고 리소스 이름은 IDC_UPDATE라고 주었습니다. 더블 클릭해 OnUpdate() 함수를 만들었습니다. 수정하는 것은 의외로 간단합니다. 지난 시간 만들었던 입력 함수에서 한 줄만 바꿔주면 됩니다. 입력 함수였던 OnInsert()에서는 AddNew()로 새로운 필드가 입력된다고 선언하고 필드 값을 채우고 Update() 함수를 호출했습니다. 이번에는 AddNew 대신 Edit() 함수를 살짝 바꿔놓으면 됩니다.

 <리스트 2> DaoDiaryDlg.cpp

void CDaoDiaryDlg::OnUpdate()
{
   // TODO: Add your control notification handler code here
   CComboBox *pYear = (CComboBox *)GetDlgItem(IDC_YEAR);
   CComboBox *pMonth = (CComboBox *)GetDlgItem(IDC_MONTH);
   CComboBox *pDate = (CComboBox *)GetDlgItem(IDC_DATE);
   CComboBox *pWeather = (CComboBox *)GetDlgItem(IDC_WEATHER);
   CEdit *pTitle = (CEdit *)GetDlgItem(IDC_TITLE);
   CEdit *pContents = (CEdit *)GetDlgItem(IDC_CONTENTS);

   int nYear = pYear->GetCurSel() + 1990;
   int nMonth = pMonth->GetCurSel() + 1;
   int nDate = pDate->GetCurSel() + 1;
   int nWeather = pWeather->GetCurSel() + 1;
   
   // 변경할 준비
   m_MyOdbcDB.Edit() // 업데이트를 위해 바뀐 부분입니다.
   // COleDateTime으로 바꾸지 않으면 에러가 납니다.
   m_MyOdbcDB.m_Date.SetDate( nYear, nMonth, nDate );
   m_MyOdbcDB.m_Weather = nWeather;
   pTitle->GetWindowText( m_MyOdbcDB.m_Title );
   pContents->GetWindowText( m_MyOdbcDB.m_Contents );
   // 수정 완료
   m_MyOdbcDB.Update();
   MessageBox("변경되었습니다.");}
}

“데이터를 수정할 때는 UPDATE 테이블 SET 필드, 그리고 WHERE 절...”
DBMS 책을 함께 바라보며 레아군은 DBMS를 조작합니다. 그러나 무심코 실행 아이콘을 누르자마자 테이블 이름을 잘못 적었다는 사실을 알게 되었고 이 잘못 입력한 테이블은 기존에 이미 존재하는, 지금 당장 서비스 중인 회원 테이블 사실을 알게 되었습니다.
“악!!”
개발실 가득히 절망적인 비명을 외치지만 일요일 오후 개발실에서 듣는 이는 아무도 없었습니다. 아마 이런 일은 DBA 권한을 갖게 된 초보 개발자들이라면 한 번씩 몰래 겪게 되는 일일 것입니다.

일기를 검색하자, WHERE 절의 사용
검색을 위한 SQL 명령어는 따로 존재하지 않습니다. SELECT나 UPDATE 등 어떤 SQL 문이라도 어떤 조건을 줄 수 있습니다. 조건을 주는 SQL 문은 WHERE라는 문장입니다. 따라서 모든 문장을 보는 SELECT 문을 하나 만들고 그 뒤에 특정한 조건만 보겠다고 지정할 수 있습니다. WHERE 절에 특정한 날짜, 특정 문장, 특정 날씨 등을 지정하면 되는 것이죠. 이렇듯 데이터베이스를 이용하면 검색이라는 어려운 알고리즘을 WHERE 문장 하나로 해결할 수 있습니다. 여기에서는 간단히 제목(Title) 필드에 특정 단어가 들어간 일기를 검색하고자 합니다. SQL 문은 어떻게 될까요?

SELECT * FROM Diary WHERE Title Like '&검색단어&’

Like는 중요한 SQL 문장입니다. Like는 문자 속에서 ‘검색 단어’란 단어가 들어가는 문장을 전부 가져옵니다. 만약 맑은 날의 일기만 보고 싶다면 다음과 같이 하면 됩니다.

SELECT * FROM Diary WHERE Weather = 1

WHERE 절은 AND 연산으로 더욱 세밀한 검색이 가능합니다. 웹 게시판에서 보는 게시판 검색 기능 역시 WHERE 절을 이용해 가져오는 것입니다. 이외에도 WHERE 절을 효과적으로 이용해 더욱 세밀한 검색 기능을 만들 수 있지만 실습은 생략하기로 합니다. SQL 문 중에서 WHERE 절의 사용법만 숙지하고 기본적인 MFC 컨트롤만 응용한다면 검색 기능의 추가는 쉽게 나오니까요.

자, 그럼 CRecordset에는 WHERE 절을 위해 어떤 함수를 제공해주고 있을까요? 이제까지와는 조금 다른 방법을 이용합니다. m_MyOdbcDB 내의 멤버들을 살펴보면 m_strFilter라는 문자열 변수가 있습니다. m_strFilter는 SQL에서 WHERE 절 바로 뒤에 붙는 문장을 의미합니다. 따라서 실제 코드 상에서는 다음과 같이 적으면 됩니다.

m_MyOdbcDB.m_strFilter = “Title Like ‘%검색단어%’”;
m_MyOdbcDB.Requery();

Requery()는 다시 SELECT를 수행한다는 의미입니다. 이것을 빠뜨리면 새로운 결과 셋이 나타나지 않습니다. 검색을 위한 새로운 윈도우를 띄우는 것도 좋겠지만 코드의 간결함을 위해 기존 창 위에 검색 에디트 박스와 버튼을 만들기로 합니다. 만약 검색 조건에 해당하는 필드가 없다면 결과 셋이 BOF거나 EOF일 경우입니다.

 <리스트 3> DaoDiaryDlg.cpp 검색 기능의 추가
void CDaoDiaryDlg::OnWhere()
{
   // TODO: Add your control notification handler code here
   CString strFilter;
   CEdit *pFilter = (CEdit *)GetDlgItem(IDC_FILTER);
   pFilter->GetWindowText( strFilter );

   CString str1("Title Like '%");
   CString str2("%'");
   CString str3 = str1 + strFilter + str2;

   m_MyOdbcDB.m_strFilter = str3;
   m_MyOdbcDB.Requery();
   // 검색 조건이 없을 때
   if ( m_MyOdbcDB.IsBOF() || m_MyOdbcDB.IsEOF() )
   {
      MessageBox("검색 조건에 맞는 필드가 없습니다.");
      m_MyOdbcDB.m_strFilter = "";
      m_MyOdbcDB.Requery();
      m_MyOdbcDB.MoveLast();
      FillContents();
      return;
   }
   FillContents();
}

그런 경우에는 <리스트 3>과 같이 에러 출력과 함께 다시 SELECT해서 결과 셋을 원상 복귀시키고 있습니다. <화면 7>은 데이터 관리가 모두 포함된 일기장입니다. 이를 통해 데이터베이스와 친숙해지기를 바랍니다.

<화면 7> 수정과 검색 기능이 포함된 ODBC 일기장

“흐흐흑, 그, 그러니까요, 저..저는 아무 일도 안했는데요. 저절로 바뀌었어요.”
“아무 일도 안했는데 왜 바뀌죠?”
레아군이 할 수 있는 일이라곤 DBMS에 대해 알고 있던 유나에게 전화를 거는 것이었습니다. 이 핑계 저 핑계를 대다 솔직히 밝히고 해결법을 묻습니다.
“일단 꾸중은 내일하고 팀장님이 알기 전에 고쳐야죠. 지금 시간이 3시 30분이니까, 마지막 백업으로 돌리는 저장 프로시저가 있어요. 불러줄테니 그것을 작동시키세요.”
“저장 프로시저는 어떻게 작동시키죠?”
“흠... MS 계열에서는 SQL 문을 전송할 때 공통적으로 Execute로 시작되는 함수를 쓰니 MSDN에서 찾아보세요.”

숨겨진 ExecuteSQL()를 찾아라
이제까지 설명한 것들이 DAO와 ODBC에서 제공해 주는 데이터베이스 관련 기능들이었습니다. MFC에서는 데이터베이스와 애플리케이션 개발자를 철저히 구분해 SQL 문장을 모르더라도 미리 제공되는 메쏘드만으로 가능하게끔 설계되어 있습니다. 무척 뛰어난 기능이지만 SQL 문을 직접 호출해야 할 경우도 있습니다. 특히 DBMS 내의 저장 프로시저를 호출할 때는 필수적이죠. 그런 경우를 대비하여 직접 SQL 문을 쓰는 방법이 필요합니다.

ExecuteSQL()라는 함수를 통해 DBMS로 SQL 문을 날릴 수가 있습니다. 하지만 ExecuteSQL 함수는 CRecordset이 아닌 CDatabase 클래스의 멤버 함수입니다. 그렇다면 직접 SQL을 이용할 때는 CDatabase를 다시 선언해야 할까요? 아닙니다. CRecordset에서도 ExecuteSQL()을 찾을 수 있습니다. 어쩌면 이번 강좌에서 최고로 유용한 비기(?)가 아닐까 합니다. 바로 다음과 같은 방법으로 ExecuteSQL()을 찾을 수 있습니다.

m_MyOdbcDB.m_pDatabase->ExecuteSQL( strSQL );

즉, m_pDatabase는 CRecordset의 멤버 변수로서 CDatabase 객체의 포인터를 가지고 있습니다. 그속에서 -> 연산자로 ExecuteSQL() 등, CDatabase의 멤버 함수들을 가져다 쓸 수 있습니다. 인자로는 CString을 그대로 이용하니 용도에 맞는 SQL 문을 그대로 만들어 보내면 됩니다. 물론 저장 프로시저도 ExecuteSQL() 문을 통해 보낼 수 있습니다. 만약 저장 프로시저로 업무가 수행된다면 MFC 개발자는 이 함수 하나로 모든 작업을 마칠 수 있을 것입니다. m_pDatabase와 ExecuteSQL()은 폐쇄적인 듯한 CRecordset 클래스에서 CDatabase를 사용하는 방법이니 절대 잊지 않도록 합시다.

다음 시간에는 OLE-DB를…
비를 좋아하는 필자라 장마가 싫지만은 않는 시기입니다. 새벽을 가리키는 시계 속에서 이번 원고도 마칠까 합니다. 무언가 대단히 쓸만한 예제를 만들려고 하였지만 데이터베이스 관련 함수들의 설명만으로도 지쳐 기능은 역시 미흡하군요. 하지만 실제 데이터베이스를 응용하는 기술은 이것으로 충분하다고 생각됩니다. 나머지는 전부 컨트롤 UI에 대한 코드 추가뿐입니다. 이 일기장 프로그램을 보다 쓸만한 일기장으로 변신시켜보세요.

이번 시간에는 DAO 때와 달리 ODBC를 이용해 데이터베이스에 연결했습니다. ODBC의 특징은 애플리케이션과 DBMS 사이의 브릿지(다리)라는 사실이며 DBMS가 다르다 할지라도 같은 함수로 작동된다는 사실입니다. 따라서 엑세스로 만들었던 일기장의 초기 코드도 아무런 수정 없이 잘 작동되었음을 목격하였습니다. 만약 MS SQL 서버가 없는 독자라 할지라도 엑세스나 MySQL 등 익숙한 DBMS로 ODBC 연결을 만들어 이번 예제를 작동시켜 보기 바랍니다. 지금 시각으로는 단순한 것 같지만 ODBC의 출현에 환호했던 약 10년 전의 개발자 선배님들을 상상하면서 말입니다. 다음 시간에는 OLE-DB를 이용해 또 다른 작업을 해보려 합니다. OLE란 말이 들어가니까 COM에 관한 내용을 미리 읽어보세요.

다행히 유나가 불러준 롤백(Roll Back) 저장 프로시저를 ExecuteSQL() 문으로 작동시켰지. 감탄했던 것은 DBMS 로그인 정보까지 교묘히 지워주더군. 한마디로 이럴 때 쓰라고 만든 저장 프로시저 같았어. 경외스런 맘으로 저장 프로시저를 천천히 읽어보니 작성자가 유나잖아. 혹시 유나도 이런 일을 자주 겪었던 것은 아닐까? 흠... 이런 일 정도는 훌륭한 개발자가 되기 위한 관문이라 생각하자구. @

전체 소스 다운로드

http://www.zdnet.co.kr/builder/dev/db/0,39031604,39131033,00.htm

반응형

'WindowsPrograming' 카테고리의 다른 글

MFC 요점 강의  (0) 2007.03.11
API/MFC차이점  (0) 2007.03.11
UpdateWindow(), InvalidateRect()  (0) 2007.03.10
Posted by Real_G