JDBC(Java Database Connectivity)는 Java에서 관계형 데이터베이스에 접근하고 조작하기 위한 API입니다. Java 애플리케이션이 DBMS(Database Management System) 종류와 관계없이 일관된 방식으로 데이터베이스에 연결하고 쿼리할 수 있게 해줍니다.
JDBC는 Java 애플리케이션이 데이터베이스에 접근할 수 있도록 지원하는 여러 컴포넌트로 구성되어 있습니다.
JDBC는 Two-Tier와 Three-Tier 두 가지 아키텍처로 사용할 수 있습니다.
JDBC 드라이버는 Class.forName()을 통해 로드되며, 이는 Java의 Reflection API를 사용합니다. 직접 드라이버 클래스를 인스턴스화하지 않는 이유는 애플리케이션과 드라이버 간의 결합을 줄이기 위함입니다. 또한, 드라이버 로딩은 전체 애플리케이션에서 한 번만 수행하는 것이 좋습니다.
데이터베이스 쿼리 실행 시 두 가지 주요 방식이 있습니다:
따라서, 반복적이거나 대량 작업에서는 PreparedStatement를 사용하는 것이 메모리와 성능 관리 측면에서 더욱 유리합니다.
ResultSet 객체는 쿼리 결과를 나타내며, 커서(cursor)를 통해 결과 집합의 현재 행을 가리킵니다. 초기에는 결과 집합의 첫 번째 행 이전을 가리키고 있으며, next() 메서드를 통해 다음 행으로 이동할 수 있습니다. ResultSet은 행 단위로 데이터를 처리할 수 있는 다양한 메서드를 제공합니다.
JDBC에서는 데이터베이스 트랜잭션을 제어하기 위한 기능도 제공합니다. 트랜잭션은 여러 데이터베이스 작업을 한 단위로 묶어 관리할 수 있으며, 자동 커밋 모드를 비활성화하고 commit()이나 rollback() 메서드를 사용하여 트랜잭션을 직접 관리할 수 있습니다. 트랜잭션 관리는 데이터 일관성을 유지하고 오류 발생 시 복구하기 위해 중요합니다.
트랜잭션 수동 제어 시 발생할 수 있는 오류와 해결 방법
JDBC에서 트랜잭션을 수동으로 제어하려면 자동 커밋을 비활성화하고 commit()과 rollback()을 사용하여 작업을 완료해야 합니다. 하지만 잘못된 트랜잭션 처리 방식은 데이터 일관성 문제를 초래할 수 있으므로 주의가 필요합니다.
문제: 트랜잭션이 종료되지 않고 방치되면 다른 사용자가 동일한 데이터를 접근하는 데 문제가 생길 수 있습니다. commit()이나 rollback()을 호출하지 않으면 트랜잭션이 영구히 종료되지 않는 상태가 될 수 있습니다.
해결 방법: 트랜잭션 작업이 끝나면 반드시 commit()이나 rollback()을 호출해야 합니다. 일반적으로 try-catch-finally 블록을 사용해 예외 발생 시 자동으로 롤백하고, 작업 종료 후 커밋이 되도록 처리합니다.
Connection conn = null;
try {
conn = DriverManager.getConnection(DB_URL, USER, PASS);
conn.setAutoCommit(false); // 트랜잭션 시작
// 트랜잭션 처리 작업
conn.commit(); // 작업 완료 후 커밋
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback(); // 예외 발생 시 롤백
} catch (SQLException rollbackEx) {
rollbackEx.printStackTrace();
}
}
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException closeEx) {
closeEx.printStackTrace();
}
}
}
JDBC 드라이버 타입별 특징과 사용 사례
JDBC 드라이버는 네 가지 유형으로 나뉘며, 각각의 드라이버는 특정 환경에 적합한 장단점을 갖고 있습니다.
PreparedStatement는 SQL 쿼리를 미리 컴파일하고 캐시하기 때문에 성능을 크게 개선할 수 있습니다. 특히 반복적인 쿼리 작업에서 유용하며, 대량 데이터를 처리할 때 효과적입니다.
예시: 배치 작업을 통한 대량 데이터 삽입
PreparedStatement의 배치 기능을 활용하면 대량의 데이터를 효율적으로 삽입할 수 있습니다. 배치 기능을 사용하면 여러 SQL 문을 한 번에 보내므로 네트워크 왕복 횟수가 줄어 성능이 향상됩니다.
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DriverManager.getConnection(DB_URL, USER, PASS);
conn.setAutoCommit(false); // 자동 커밋 비활성화
String sql = "INSERT INTO Employees (id, name, position) VALUES (?, ?, ?)";
pstmt = conn.prepareStatement(sql);
for (int i = 1; i <= 1000; i++) {
pstmt.setInt(1, i);
pstmt.setString(2, "Employee" + i);
pstmt.setString(3, "Position" + i);
pstmt.addBatch(); // 배치에 추가
if (i % 100 == 0) { // 100개마다 실행
pstmt.executeBatch();
conn.commit(); // 커밋
}
}
pstmt.executeBatch(); // 남은 배치 실행
conn.commit(); // 마지막 커밋
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback(); // 오류 시 롤백
} catch (SQLException rollbackEx) {
rollbackEx.printStackTrace();
}
}
e.printStackTrace();
} finally {
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException closeEx) {
closeEx.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException closeEx) {
closeEx.printStackTrace();
}
}
}