JDBC (Java Database Connectivity)
JDBC(Java Database Connectivity)는 Java 프로그램에서 데이터베이스에 접근하여 SQL 문을 실행하고, 그 결과를 가져올 수 있게 해주는 표준 API. JDBC를 이용하면 DBMS(Database Management System) 종류에 관계없이 일관된 방식으로 데이터베이스와 상호작용할 수 있다. (예를들어, 데이터베이스를 Oracle에서 MySQL로 바꾸더라도 JDBC 코드를 크게 변경할 필요가 없다.)
1. JDBC의 역할 및 특징:
- 데이터베이스 연결: Java 프로그램과 데이터베이스 간의 연결을 설정하고 관리.
- SQL 문 실행: 데이터베이스에 SQL 쿼리(SELECT, INSERT, UPDATE, DELETE 등)를 전송하고 실행.
- 결과 처리: SQL 쿼리 실행 결과를 Java 프로그램에서 사용할 수 있는 형태로 가져옴. (ResultSet)
- 트랜잭션 관리: 여러 SQL 문을 하나의 논리적인 작업 단위(트랜잭션)로 묶어 처리. (commit, rollback)
- DBMS 독립성: JDBC API는 표준 인터페이스를 제공하므로, 특정 DBMS에 종속되지 않는 코드를 작성할 수 있다.
- 예외 처리: 데이터베이스 작업 중 발생하는 예외(SQLException)를 처리 가능.
2. JDBC API 구성 요소:
- java.sql 패키지: JDBC API의 핵심 인터페이스와 클래스들을 포함.
- DriverManager: JDBC 드라이버를 로드하고, 데이터베이스 연결(Connection)을 생성하는 역할.
- Connection: 데이터베이스와의 연결을 나타내는 인터페이스. SQL 문 실행, 트랜잭션 관리, 연결 종료 등의 기능을 제공.
- Statement: SQL 문을 실행하는 객체. 정적인 SQL 문을 실행할 때 사용.
- PreparedStatement: 미리 컴파일된 SQL 문을 실행하는 객체. 동일한 SQL 문을 반복적으로 실행하거나, 파라미터를 사용하여 동적으로 SQL 문을 구성할 때 사용. (SQL Injection 공격 방지에 유리)
- CallableStatement: 데이터베이스의 저장 프로시저(Stored Procedure)를 호출하는 객체.
- ResultSet: SQL 쿼리 실행 결과를 저장하는 객체. 테이블 형태의 데이터를 행(row)과 열(column)로 접근.
- ResultSetMetaData: ResultSet의 메타데이터(컬럼 이름, 타입, 개수 등)를 제공하는 객체.
- DatabaseMetaData: 데이터베이스 자체의 메타데이터(테이블, 뷰, 인덱스, 사용자 등)를 제공하는 객체.
- SQLException: 데이터베이스 작업 중 발생하는 예외를 나타내는 클래스.
- JDBC 드라이버:
- 특정 DBMS와 통신하기 위한 구현체. 각 DBMS 벤더(Oracle, MySQL, PostgreSQL 등) 또는 서드파티에서 제공.
- JDBC 드라이버는 java.sql.Driver 인터페이스를 구현.
3. JDBC를 이용한 데이터베이스 프로그래밍 단계:
- 1. JDBC 드라이버 로드: Class.forName() 메서드를 사용하여 JDBC 드라이버 클래스를 로드. (최근 JDBC 드라이버는 자동 로딩을 지원하므로 생략 가능)
Java
Class.forName("com.mysql.cj.jdbc.Driver"); // MySQL Connector/J 8.x
// 또는
Class.forName("org.postgresql.Driver"); // PostgreSQL
- 2. 데이터베이스 연결 (Connection 객체 생성): DriverManager.getConnection() 메서드를 사용하여 데이터베이스에 연결하고, Connection 객체를 얻는다.
- url: JDBC URL. DBMS 종류, 호스트, 포트, 데이터베이스 이름 등을 지정.
- user: 데이터베이스 사용자 이름
- password: 데이터베이스 사용자 비밀번호
Java
String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC"; // MySQL
// 또는
String url = "jdbc:postgresql://localhost:5432/mydb"; //PostgreSQL
String user = "dbuser";
String password = "dbpassword";
Connection connection = DriverManager.getConnection(url, user, password);
- 3. Statement (또는 PreparedStatement, CallableStatement) 객체 생성: Connection 객체의 createStatement(), prepareStatement(), prepareCall() 메서드를 사용하여 SQL 문을 실행할 객체를 생성.
Java
Statement statement = connection.createStatement();
// 또는
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, userId); // 파라미터 설정
- 4. SQL 문 실행: Statement, PreparedStatement, CallableStatement 객체의 메서드를 사용하여 SQL 문을 실행.
- executeQuery(String sql): SELECT 문 실행. ResultSet 객체 반환.
- executeUpdate(String sql): INSERT, UPDATE, DELETE 문 실행. 영향을 받은 행의 수(int) 반환.
- execute(String sql): 모든 종류의 SQL 문 실행. boolean 값 반환 (true: ResultSet 반환, false: 영향을 받은 행의 수 반환 또는 결과 없음)
- PreparedStatement의 경우:
- preparedStatement.executeQuery()
- preparedStatement.executeUpdate()
- preparedStatement.execute()
- 5. 결과 처리 (ResultSet): SELECT 문 실행 결과는 ResultSet 객체에 저장됨 . ResultSet 객체의 메서드를 사용하여 결과 데이터에 접근.
- next(): 다음 행으로 이동. 다음 행이 있으면 true, 없으면 false 반환.
- getXXX(columnIndex) 또는 getXXX(columnLabel): XXX 타입의 값을 가져옴 (예: getInt, getString, getDate, getBoolean, getDouble 등). columnIndex는 1부터 시작.
Java
ResultSet resultSet = statement.executeQuery("SELECT * FROM users");
while (resultSet.next()) { // 다음 행으로 이동 (없으면 false)
int id = resultSet.getInt("id"); // 컬럼 이름으로 값 가져오기
String name = resultSet.getString("name");
String email = resultSet.getString(3); // 컬럼 인덱스(1부터 시작)로 값 가져오기
// ...
System.out.println(id + ", " + name + ", " + email);
}
- 6. 자원 해제: 사용한 자원(ResultSet, Statement, Connection)을 닫습니다. (close() 메서드 호출)
Java
resultSet.close();
statement.close();
connection.close();
4. 트랜잭션 관리:
- 트랜잭션: 여러 SQL 문을 하나의 논리적인 작업 단위로 묶는 것. 트랜잭션 내의 모든 SQL 문은 모두 성공(commit)하거나, 하나라도 실패하면 모두 실패(rollback)해야 한다. (ACID 속성 보장)
JDBC 트랜잭션 관리:
- Connection 객체의 setAutoCommit(false)를 호출하여 자동 커밋 모드를 해제.
- 트랜잭션 내에서 SQL 문들을 실행.
- 모든 SQL 문이 성공하면 connection.commit()을 호출하여 변경 사항을 데이터베이스에 반영.
- SQL 문 실행 중 예외가 발생하면 connection.rollback()을 호출하여 트랜잭션 시작 전 상태로 되돌린다.
Java
try {
connection.setAutoCommit(false); // 자동 커밋 해제
// ... 여러 SQL 문 실행 ...
connection.commit(); // 트랜잭션 커밋
} catch (SQLException e) {
connection.rollback(); // 트랜잭션 롤백
} finally {
try {
if (resultSet != null) resultSet.close();
if (statement != null) statement.close();
if (connection != null) connection.close();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
5. PreparedStatement 사용의 이점:
- 성능 향상: 동일한 SQL 문을 반복 실행할 때, PreparedStatement는 한 번만 컴파일(parse)되고 재사용되므로 성능이 향상됨.
- SQL Injection 공격 방지: 파라미터 값을 SQL 문에 직접 삽입하는 대신, 바인딩 변수(?)를 사용하고 setXXX() 메서드로 값을 설정하므로 SQL Injection 공격에 안전.
- 가독성 및 유지보수성 향상: SQL 문과 파라미터 값이 분리되어 코드가 더 깔끔해지고 유지보수가 쉬워진다.
6. JDBC 예제 (MySQL):
Java
import java.sql.*;
public class JdbcExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
String user = "dbuser";
String password = "dbpassword";
try (Connection connection = DriverManager.getConnection(url, user, password); // try-with-resources 문
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT id, name, email FROM users")) {
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String email = resultSet.getString("email");
System.out.println(id + ", " + name + ", " + email);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}