PDO(PHP Data Object)는 이기종 데이터베이스에 접근하는 공통 API를 제공하는 것을 목적으로 개발되었습니다. 기존의 mysqli가 객체 스타일과 절차적 스타일을 모두 지원하는 것과 달리, PDO는 객체 스타일 API만 제공합니다.
또한, 과거에 사용되던 mysql API와 달리 Prepared Statement를 지원하므로 웹 보안의 핵심인 SQL Injection 방어에 매우 유용합니다.
💡 Prepared Statement의 역할
원래는 SQL 구문을 데이터베이스에서 미리 컴파일해 두고 파라미터 값만 바꿈으로써 쿼리 처리 성능을 높이기 위해 사용되었으나, 최근에는 파라미터를 안전하게 분리 바인딩하여 SQL Injection을 방지하는 목적으로 주로 사용됩니다.
* PDO를 사용하기 위해서는 PHP 5.1.0 이상의 버전이 필요합니다.
1. 데이터베이스 연결하기
데이터베이스에 연결하려면 PDO 객체 생성자에 DSN(Data Source Name), 사용자 아이디, 비밀번호를 인자로 전달합니다. (DSN의 port 기본값인 3306은 생략 가능합니다.)
<?php
$dsn = "mysql:host=localhost;port=3306;dbname=testdb;charset=utf8";
try {
$db = new PDO($dsn, "testuser", "testuser");
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "데이터베이스 연결 성공!!<br/>";
} catch(PDOException $e) {
echo $e->getMessage();
}
?>
주요 속성(Attribute) 설정
- PDO::ATTR_EMULATE_PREPARES : Prepared Statement를 데이터베이스가 지원하지 않을 때 에뮬레이션하는 기능입니다. false로 지정하여 데이터베이스 고유의 Prepared Statement 기능을 사용하도록 설정합니다.
- PDO::ATTR_ERRMODE : PDO 객체의 에러 처리 방식을 결정합니다.
- PDO::ERRMODE_EXCEPTION : 에러 발생 시 PDOException을 던지며, try-catch 구문으로 예외 처리를 진행합니다.
- PDO::ERRMODE_SILENT : 에러 코드만 설정합니다.
- PDO::ERRMODE_WARNING : E_WARNING 메세지를 발생시킵니다.
2. 데이터 조회하기 (SELECT)
<?php
$dsn = "mysql:host=localhost;port=3306;dbname=testdb;charset=utf8";
try {
$db = new PDO($dsn, "testuser", "testuser");
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$keyword = "%테스트%";
$no = 1;
$query = "SELECT num, name FROM tb_test WHERE name LIKE ? AND num > ?";
$stmt = $db->prepare($query);
$stmt->execute(array($keyword, $no));
$result = $stmt->fetchAll(PDO::FETCH_NUM);
for($i = 0; $i < count($result); $i++) {
printf ("%s : %s <br />", $result[$i][0], $result[$i][1]);
}
} catch(PDOException $e) {
echo $e->getMessage();
}
?>
기본 처리 순서
- 쿼리문 내에서 동적으로 입력할 값에 물음표 (? - Placeholder)를 작성합니다.
- $stmt = $db->prepare($query); : Statement 객체를 생성합니다.
- $stmt->execute(array($keyword, $no)); : 순서대로 바인딩할 파라미터 배열을 넘겨 실행합니다.
- $result = $stmt->fetchAll(PDO::FETCH_NUM); : 결과를 조회하여 배열 형태로 가져옵니다.
주요 Fetch 모드 옵션
- PDO::FETCH_NUM : 숫자로 인덱싱된 배열 반환
- PDO::FETCH_ASSOC : 컬럼명이 키(Key)인 연관 배열 반환
- PDO::FETCH_BOTH : 숫자 인덱스와 컬럼명 연관 배열 모두 포함 (기본값)
- PDO::FETCH_OBJ : 컬럼명이 프로퍼티인 익명 객체 형태 반환
* 만약 파라미터가 없는 단순 쿼리라면 $stmt->execute(); 형태로 배열 없이 호출하면 됩니다.
파라미터 바인딩 방식 (bindParam / bindValue)
1) 위치 기반 바인딩 (Positional Placeholder - ?)
<?php
$keyword = "%테스트%";
$no = 1;
$query = "SELECT num, name FROM tb_test WHERE name LIKE ? AND num > ?";
$stmt = $db->prepare($query);
$stmt->bindParam(1, $keyword, PDO::PARAM_STR);
$stmt->bindParam(2, $no, PDO::PARAM_INT);
// 또는 bindValue 사용
// $stmt->bindValue(1, $keyword, PDO::PARAM_STR);
// $stmt->bindValue(2, $no, PDO::PARAM_INT);
$stmt->execute();
?>
2) 이름 기반 바인딩 (Named Placeholder - :name)
<?php
$keyword = "%테스트%";
$no = 1;
$query = "SELECT num, name FROM tb_test WHERE name LIKE :name AND num > :num";
$stmt = $db->prepare($query);
$stmt->bindParam(':name', $keyword, PDO::PARAM_STR);
$stmt->bindParam(':num', $no, PDO::PARAM_INT);
// 또는 bindValue 사용
// $stmt->bindValue(':name', $keyword, PDO::PARAM_STR);
// $stmt->bindValue(':num', $no, PDO::PARAM_INT);
$stmt->execute();
?>
3) while 구문을 이용한 단건 페치 (fetch)
<?php
while ($row = $stmt->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_NEXT)) {
printf ("%s : %s <br />", $row[0], $row[1]);
}
?>
3. 입력, 수정, 삭제 및 트랜잭션(Transaction) 처리
트랜잭션 처리를 위해 beginTransaction(), commit(), rollback() 메소드를 사용합니다.
<?php
$dsn = "mysql:host=localhost;port=3306;dbname=testdb;charset=utf8";
try {
$db = new PDO($dsn, "testuser", "testuser");
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$name = "테스트";
$query = "INSERT INTO tb_test (name) VALUES (?)";
$stmt = $db->prepare($query);
try {
// 트랜잭션 시작
$db->beginTransaction();
$stmt->execute(array($name));
// 트랜잭션 커밋
$db->commit();
// 새로 추가된 AUTO_INCREMENT ID 가져오기
$id = $db->lastInsertId();
} catch(PDOException $e) {
// 에러 발생 시 롤백
$db->rollback();
echo $e->getMessage();
}
} catch(PDOException $e) {
echo $e->getMessage();
}
?>