저자: 한동훈(traxacun @ unitel.co.kr)
준비하기
여기서는 LAMP(Linux, Apache, MySQL, PHP)가 설치되어 있는 환경에서 데이터 액세스 프로그래밍에 대해서 설명합니다. 설치 방법에 대해서는 다양한 책과 인터넷에 설명되어 있으므로 여기에 다시 설명하지 않아도 충분히 설치할 수 있을거라 생각합니다. 리눅스 배포판마다 조금씩 차이가 있어서 각자가 사용중인 배포판에 맞게 설치해야 합니다. 제 경우엔 데비안(Debian) 리눅스를 사용하고 있어서, 국내에 널리 쓰이고 있는 레드햇 환경에서의 설치에 대해서는 문외한이라는 문제점도 있습니다. ^^;
소개
이 글은 기본적인 PHP 데이터 연결에 대해서 살펴보고, 이러한 데이터 액세스 라이브러리를 구축하는 것에 대해서 살펴볼 생각입니다. 마지막으로는 많은 동시 사용자를 처리하기 위한 연결 풀링(Connection Pooling)에 대해 이야기할 겁니다. 뭐, 그렇다고 그렇게 어려운 것도 아니고, 처음부터 설명할 것이니 천천히 함께 나아가면 감사하겠습니다. 이미, PHP에 익숙한 분들이라면 이 글을 더 이상 안 봐도 될 것 같습니다. 물론, 봐주신다면 영광...^^
PHP Data Access 기본 함수
1. 데이터베이스 연결
데이터베이스에 연결하기 위해서 mysql_connect()를 사용합니다.
$dc = mysql_connect( "localhost", "userName", "password" );
Apache와 MySQL이 같은 서버에 설치되어 있다면 "localhost"라고 입력하지만, MySQL이 별도의 서버에 설치되어 있다면 “192.168.0.2”와 같이 MySQL이 설치된 서버의 IP를 지정합니다.(물론, MySQL에서의 설정도 필요하지만, 그건 MySQL 도움말을 참고하세요)
mysql_connect()를 사용하는 전형적인 예는 die()를 사용하여 연결을 생성하지 못하는 경우에 에러 메시지를 표시하는 것입니다.
$dc = mysql_connect( "localhost", "userName", "password" ) or die( "연결할 수 없습니다" );
or 부분은 앞부분의 실행이 실패할 경우에 이 다음 부분을 실행하라는 것을 의미합니다. DB 연결이 생성된다면 실행되지 않는 부분을 의미합니다.
코드의 어느 부분에서도 데이터베이스를 지정하지 않았습니다. 그래서 보통의 PHP 프로그래머들은 mysql_connect()를 사용한 다음에 반드시 mysql_select_db()를 사용하여 데이터베이스를 지정합니다. 따라서, 전형적인 데이터베이스 연결 코드는 다음과 같이 작성됩니다.
$dc = mysql_connect( "localhost", "username", "password" );
mysql_select_db( "database", $dc );
$dc라는 이름은 Database Connection이라는 의미로 사용하고 있습니다. 사람들에 따라서 $conn, $db등을 사용합니다.
여러가지 이유로 MySQL 서버의 포트 번호를 기본 포트 3306이 아닌 다른 포트를 지정한 경우에는 다음과 같이 mysql_connect()를 사용할 수 있습니다.
$dc = mysql_connect( "localhost:4004", "username", "password" );
mysql_connect()와 함께 잘 알려진 데이터베이스 연결함수는 mysql_pconnect()가 있습니다. 이 둘 간의 동작 방식에 대해서는 나중에 자세히 설명하겠습니다.
2. 데이터베이스 연결 닫기
생성된 데이터베이스 연결을 닫기 위해서는 mysql_close()를 사용합니다.
mysql_close( $dc );
메모: mysql_close()를 볼 때마다 PHP의 명명법에 대해서 얘기하지 않을 수 없군요. 보통 함수의 이름을 지을 때는 open, close라든가 connect, disconnect 라든가, get, set 처럼 서로 짝을 이루는 이름을 사용합니다. 그래야 보다 직관적으로 코드를 이해할 수 있습니다. 다른 언어들의 데이터 액세스 코드들을 보면 이처럼 짝을 이루는데, PHP에서는 DB 연결에는 connect를 사용하고, disconnect 대신에 close를 사용하고 있습니다. 하지만, 여러분의 라이브러리에서는 보다 적절한 이름을 택하도록 하세요.
3. 데이터베이스 쿼리
데이터베이스에서 어떤 값을 가져오기 위해 mysql_query()를 사용합니다. mysql_query()를 사용하여 반환된 레코드 수는 mysql_num_rows()로 알 수 있습니다. 보통 쿼리를 실행한 결과는 여러개의 레코드로 구성되어 있으므로 레코드를 하나씩 분리하여 처리할 필요가 있습니다. 그래서 레코드를 가져오기(fetch)위해 mysql_fetch_row()를 사용합니다.
저는 우편번호 데이터베이스를 이용했습니다. 여러분이 사용하는 데이터베이스에 맞게 쿼리를 적절히 변경하세요.(앞에서 설명한 연결까지 포함하여 전체 코드를 옮겨둡니다)
$dc = mysql_connect( "localhost", "USERNAME", "PASSWORD" );
mysql_select_db( "DATABASE" );
$query = "SELECT p_num, addr FROM zipcode limit 0, 2";
$result = mysql_query( $query, $dc );
for( $ctr = 0; $ctr mmlt; mysql_num_rows( $result ); $ctr++ )
{
$rows = mysql_fetch_row( $result );
echo $rows[0]." -mmgt; ".$rows[1]."mmlt;brmmgt;";
}
mysql_close( $dc );
mysql_query()와 같이 쿼리를 실행하면 데이터베이스는 쿼리문에 문제가 없다면 결과값을 반환합니다. 이 결과값을 받아올 그릇(Container)이 필요합니다. 그래서 $result라는 그릇에 이 값을 받아두지만 그릇의 형태를 지정하지 않았습니다.
mysql_fetch_row()는 결과값을 하나의 열(row)로 가져오는 것이며, mysql_fetch_array()는 결과값을 배열로 가져오는 것입니다. 결과값을 어떤 형태로 이용할 것인가는 사용자의 취향에 따라 결정되는 것이기도 하고, 편의성에 따라 결정되기도 합니다.
$rows[0]와 $rows[1]에서 알 수 있는 것과 같이 0과 1은 쿼리에 지정된 필드 순서입니다.
$query = "SELECT p_num, addr FROM zipcode limit 0, 2";
위 쿼리를 “SELECT addr, p_num FROM zipcode limit 0, 2”로 실행한다면 순서가 뒤 바뀌어 출력될 것입니다.
실행결과는 다음과 같습니다.
135814 -> 서울특별시 강남구 논현1동 신동아아파트
135749 -> 서울특별시 강남구 논현1동 영풍빌딩
위 코드에서는 레코드를 보여주기 위해 for 루프를 사용했습니다. 보통의 예제들은 다음과 같이 while 루프를 사용합니다.
while( $rows = mysql_fetch_row( $result ) )
{
echo $rows[0]." -mmgt; ".$rows[1]."mmlt;brmmgt;";
}
모두 동일한 결과를 보여줍니다. 제 경우에는 while 보다 for 루프를 선호합니다. $ctr과 같은 카운트 변수를 이용하여 정확하게 원하는 횟수만큼 루트를 반복시키는 것이 while보다 용이하기 때문입니다. 그러나, 이것 역시 독자의 취향에 달려있습니다.
3.1 결과값을 가져오는 그릇(Container)
앞의 예제에서 쿼리문에 지정된 필드 순서에 따라 출력 결과가 뒤바뀔 수 있다고 말했습니다. 게다가, $rows[0], $rows[1]과 같이 숫자를 이용하는 것은 그다지 직관적이지 않습니다. mysql_fetch_array()는 배열 형태로 데이터를 담아줄 뿐만 아니라 필드 이름을 사용할 수 있게 해줍니다. 앞의 예제를 mysql_fetch_array()로 변경하면 다음과 같습니다.
while( $rows = mysql_fetch_array( $result ) )
{
echo $rows[p_num]." -mmgt; ".$rows[addr]."mmlt;brmmgt;";
}
그릇의 종류는 그다지 많지 않습니다. 가장 많이 쓰이는 것은 mysql_fetch_row(), mysql_fetch_array() 정도지요. 다른 언어를 사용한다면 RecordSet, DataSet, Repeater, DataReader 등의 이름을 볼 수 있습니다. 하지만, 이들 모두 결과값을 담아오는 그릇의 형태와 크기, 용도의 차이만 있을 뿐 본질적으로는 같습니다.
4. 메타 데이터 함수
메타 데이터라는 것은 데이터 자신에 대한 정보를 말해주는 데이터입니다. "인간"이라는 객체가 있어도 "철수", "영이"는 각각 다른 데이터를 갖고 있습니다. "철수는 인간이다"라는 정보로는 프로그래머가 할 수 있는 일이 거의 없습니다. 그렇다고 프로그래머가 철수에 대한 정보를 모두 알아서 처리해야 하고, 영이에 대한 정보를 직접 알아서 처리해야 한다면, 새로운 데이터가 추가될 때마다 계속해서 작업을 해야할 겁니다.
만약에 철수가 자신을 설명할 수 있고, 프로그램은 그 설명을 토대로 처리를 자동화할 수 있다면 편하지 않을까요?
메타 데이터라는 것은 이러한 자동화를 위해 필요합니다.
반환되는 결과값에 포함된 필드 수를 알기 위해 mysql_num_fields()를 사용하며, 필드 이름을 알기 위해 mysql_field_name()을 사용합니다. 각 필드의 데이터 타입을 알기 위해 mysql_field_type()을 사용합니다.
앞의 예제에서 루프 부분만 다음과 같이 변경합니다.
for( $ctr = 0; $ctr mmlt; mysql_num_fields( $result ); $ctr ++ )
{
echo " ".mysql_field_name( $result, $ctr );
echo "(".mysql_field_type( $result, $ctr ).")";
}
echo "
";
for( $ctr = 0; $ctr mmlt; mysql_num_rows( $result ); $ctr++ )
{
$rows = mysql_fetch_row( $result );
echo $rows[0]." -mmgt; ".$rows[1]."mmlt;brmmgt;";
}
실행결과는 다음과 같습니다.
p_num(string) addr(string)
135814 -mmgt; 서울특별시 강남구 논현1동 신동아아파트
135749 -mmgt; 서울특별시 강남구 논현1동 영풍빌딩
첫번째 루프는 각 필드 이름과 데이터 타입을 알아오기 위해 사용했습니다.
코드에서는 각 레코드를 보여주기 위해 $rows[0]과 $rows[1]을 사용했어도 첫번째는 p_num 필드이며, 두번째는 addr 필드라는 사실을 알 수 있습니다. 즉, 숫자를 이용한 필드 접근은 프로그래머를 위한 것이 아니라 자동화를 위해 필요한 것입니다. ^^
지금까지 배운 PHP Data Access 함수들을 토대로 간단한 SQL Editor를 만들어 보겠습니다.
SQL Editor 만들기
쿼리를 입력할 수 있는 간단한 폼을 만들었습니다. 코드를 간단하게 하기 위해 $html 변수를 이용하여 테이블을 작성하였습니다.
mmlt;form action="mmlt;?= $PHP_SELF; ?mmgt;" method="post"mmgt;
mmlt;textarea name="query" rows="15" cols="60"mmgt;mmlt;?= $query; ?mmgt;mmlt;/textareammgt;
mmlt;brmmgt;
mmlt;input type="submit" value="Execute"mmgt;
mmlt;/formmmgt;
mmlt;?
if( !$submit )
{
$dc = mysql_connect( "localhost", "USERNAME", "PASSWORD" );
mysql_select_db( "DATABASE" );
$query = addSlashes( $_POST[ query ] );
$result = mysql_query( $query, $dc );
$html = "mmlt;table border=1 cellspacing=0mmgt;mmlt;trmmgt;";
for( $ctr = 0; $ctr mmlt; mysql_num_fields( $result ); $ctr ++ )
{
$html = $html."mmlt;tdmmgt;".mysql_field_name( $result, $ctr );
$html = $html."(".mysql_field_type( $result, $ctr ).")";
$html = $html."mmlt;/tdmmgt;";
}
$html = $html."mmlt;/trmmgt;";
for( $ctr = 0; $ctr mmlt; mysql_num_rows( $result ); $ctr++ )
{
$html = $html."mmlt;trmmgt;";
$row = mysql_fetch_row( $result );
for( $ctr2 = 0; $ctr2 mmlt; mysql_num_fields( $result ); $ctr2++ )
{
$html = $html."mmlt;tdmmgt;".$row[ $ctr2 ]."mmlt;/tdmmgt;";
}
$html = $html."mmlt;/trmmgt;";
}
$html = $html."mmlt;/tablemmgt;";
echo $html;
mysql_close( $dc );
}
?mmgt;
예제를 실행한 결과는 다음과 같습니다.
결과에서 볼 수 있는 것처럼 쿼리의 개수에 관계없이 동작하는 에디터를 만들었습니다. 이처럼 자기 자신을 설명해주는 함수가 있으면 많은 작업을 자동화할 수 있습니다. 사실, mysql_field_type()과 같이 자신을 설명하는 함수를 PHP 프로그래머들은 메타 데이터 함수라고 말하지 않습니다. 이건 그냥 제가 멋대로 불러버린 이름이지요.
그러나 이러한 기능은 대부분의 언어에서 제공하고 있으며, 자바, 닷넷과 같은 언어에서는 방대한 메타 데이터를 제공할 뿐만 아니라 메타 데이터를 직접 수정할 수 있는 리플렉션(reflection)을 지원합니다. 사람이 자신의 모습을 알기 위해서는 거울에 비친 상(리플렉션)을 봐야 하겠지요? 프로그래밍도 마찬가지입니다. 그리고 이를 이용해 우리는 많은 작업을 할 수 있습니다.
다음에는 여기서 배운 것들을 토대로 라이브러리를 작성해 보겠습니다. 물론, 라이브러리 작성이라는 것은 쉬운 작업이 아니며 대부분의 PHP Data Access 함수들을 알아야 합니다.
Data Access 공통
여러분이 어떤 프로그래밍 언어와 라이브러리를 사용하더라도 데이터 액세스 프로그래밍에는 공통된 것들을 발견할 수 있습니다. 언어와 구현상의 차이점은 있어도 기반 아이디어가 동일하기 때문에 이러한 공통점을 쉽게 알 수 있습니다.
데이터 액세스 프로그래밍은 보통 다음과 같은 단계를 거칩니다.
- 데이터베이스에 연결을 한다.
- 데이터베이스에 SQL 쿼리(query)를 보낸다. 쿼리를 보내면 데이터베이스 서버에서는 어떤 결과값을 돌려줍니다. 그러면 그 결과값을 받아올 그릇(Container)이 필요합니다. 그릇은 언어마다 다르겠지요.
- 가져온 데이터를 처리한다.
- 데이터베이스와 연결을 종료한다.
2번의 쿼리는 다양한 쿼리가 될 수 있습니다. SELECT, DELETE, CREATE, UPDATE 무엇이든 될 수 있습니다. DELETE라 하더라도 어떤 값을 반환하는 것은 마찬가지입니다. ^^
그리고 라이브러리 작성은 2번과 3번을 추상화하는 작업입니다.
마치며
데이터베이스 액세스 공통에 대해 이야기하고 각각을 설명하는 것이 좋았을지도 모르겠습니다. 이 부분을 이야기하는 것, 즉 전체적인 흐름을 이야기하는 것이 어떻게(How) 코드를 작성하는 지 설명하는 것 보다 중요하다고 생각합니다. 왜(Why) 이렇게 되어 있을까, 왜(Why) 이런식으로 코드를 작성해야 할까라는 질문에 대한 답을 알고 있는 사람이 보다 나은 코드를 작성할 수 있다고 생각합니다.