MySql
[JDBC] Query 요청을 위한 Statement vs PreparedStatement
kimyongjun0129
2025. 6. 25. 00:00
목차
개요
💡 JDBC DriverManager를 통해 적절한 드라이버를 찾아 데이터베이스와 연결하고, 연결이 완료되면 `Connection` 객체를 반환받습니다.
이 `Connection` 객체를 통해 `Statement` 또는 `PreparedStatement`를 생성하여 SQL 쿼리를 실행할 수 있습니다.
`Statement`는 정적인 SQL을 문자열로 직접 실행할 때 사용하고,
`PreparedStatement`는 파라미터를 바인딩할 수 있어 보안과 성능 면에서 더 우수합니다.
Statement
정의
정적 SQL 문을 실행하고 그 실행 결과를 반환하는데 사용되는 인터페이스입니다.
특징
- 기본적으로 하나의 `Statement` 객체당 단 하나의 `ResultSet` 객체만 사용할 수 있습니다.
- 하나의 `Statement`로 여러 `ResultSet`을 동시에 사용하는 것은 불가능하며, 새 쿼리를 실행하면 기존 결과는 자동으로 닫힙니다.
- 동시다발적으로 쿼리 결과 처리가 필요하다면 여러 개의 `Statement` 객체를 사용해야 합니다.
예제 코드
try (Statement statement = connection.createStatement()) {
String selectSql = "SELECT name, age FROM USERS userId = " + userId;
ResultSet set = statement.executeQuery(creatSql);
}
- Statement는 executeQuery() 나 executeUpdate() 를 실행하는 시점에 파라미터로 SQL문을 전달하는데, 이 때 전달되는 SQL 문은 완성된 형태로 한눈에 무슨 SQL 문인지 파악하기 쉽습니다.
- 하지만, Statement 는 SQL문을 수행하는 과정에서 구문 분석을 수행하기 때문에 Prepared Statement에 비해 효율성이 떨어집니다.
Statement의 4단계
- 위 예제 코드에서 `statement.executeQuery(createSql);`이 호출되면 `Statement의 4단계`를 거칩니다.
- Statement는 단순한 문자열 기반 SQL 실행 도구로, 값과 타입을 명시적으로 바인딩하는 과정이 존재하지 않습니다.
Statement의 한계
항목 | Statement |
`?` 바인딩 지원 | ❌ 없음 |
SQL Injection 방지 | ❌ 취약 |
실행 계획 캐싱 | ❌ 없음 |
파라미터 타입 안전성 | ❌ 없음 |
- `placeholder` 바인딩을 지원하지 않기 때문에, SQL에 포함될 값을 직접 문자열로 조립해서 실행해야합니다. 이때 다음과 같은 SQL Injection 위협을 받을 수 있습니다. (보안 취약)
String username = request.getParameter("username"); // ' OR '1'='1' --
String password = request.getParameter("password"); // 임의의 값
Statement stmt = connection.createStatement();
String sql = "SELECT * FROM USERS WHERE username = '" + username + "' AND password = '" + password + "'";
ResultSet rs = stmt.executeQuery(sql);
SELECT * FROM USERS WHERE username = '' OR '1'='1' -- AND password = '임의의 값'
- `--`는 SQL에서 주석 시작 문자
- 뒤에 오는 AND password = ...는 무시됩니다.
💡 그럼 `Statement`는 언제 사용할까요? 거의 사용되지 않지만 다음과 같은 경우에 사용될 수 있습니다.
상황 | 설명 |
1회성 단순 쿼리 | 정적이고 고정된 SQL을 테스트할 때 |
내부적으로 신뢰된 값만 사용 | 외부 입력 없이 하드코딩된 SQL 실행 시 |
PreparedStatement
정의
미리 컴파일 된 SQL 문을 나타내는 인터페이스입니다.
특징
- Statement를 상속하고 있습니다.
- SQL 문은 미리 컴파일 되어 `PreparedStatement` 구현체에 저장됩니다.
- 이 구현체는 해당 SQL 문을 여러 번 효율적으로 실행하는 데 사용될 수 있습니다.
예제 코드
String selectSql = "SELECT name, age FROM USERS WHERE userId = ?";
try (PreparedStatement statement = connection.prepareStatement(selectSql)) {
statement.setInt(1, userId);
ResultSet set = statement.executeQuery();
}
- `connection.prepareStatement(selectSql)` 을 호출하는 시점
- 사용자가 입력한 SQL 문자열은 JDBC Driver 내부에서 레핑되어 서버로 전달될 준비를 합니다. 이 시점에서 `?`(placeholder)가 포함되어 있다면, 아직 값은 바인딩되지 않은 상태입니다.
- Driver는 `selectSql`(SQL)을 DB 서버에 전송합니다. 이때 DB는 해당 SQL을 파싱(parsing)하고 구문 오류를 체크합니다.
- SQL 문이 유효하면, DB는 이를 실행 가능한 형태로 컴파일하고 내부 실행 계획을 수립합니다.
- 즉, PreparedStatement를 사용하면 구문 분석(parse) 후, 서버 측에서 실행 계획(Execution Plan)을 캐싱해서 추후에 같은 SQL을 여러 번 효율적으로 실행할 수 있어 성능이 향상됩니다.
- `statement.setInt();` : `selectSql`의 `?`(placeholder) 위치에 값과 타입 정보를 JDBC Driver 내부에 저장합니다. 이 단계가 JDBC 레벨에서의 바인딩 단계입니다.
- 이렇게 SQL 구조와 값을 분리해 처리함으로써, 사용자 입력이 SQL 구문으로 해석되지 않도록 막아줍니다. (SQL Injection 예방)
- 바인딩된 값은 그저 하나의 문자열로 인식되어 처리됩니다. (OR, AND 포함되어도 그냥 하나의 문장으로 처리)
- 이렇게 SQL 구조와 값을 분리해 처리함으로써, 사용자 입력이 SQL 구문으로 해석되지 않도록 막아줍니다. (SQL Injection 예방)
- `executeQuery()` 호출 시 : 파싱된 SQL과 함께, 실제 바인딩된 값을 서버에 전달하여 최종 실행이 진행됩니다.
Statement의 4단계
- PreparedStatement는 구문 분석(parse)의 결과를 캐싱해서 과정을 생략할 수 있으므로 성능이 향상됩니다.
- 또한 바인딩된 값도 전달 받을 수 있으므로 성능이 향상됩니다.
💡`CREATE TABLE`과 같은 DDL은 일반적으로 PreparedStatement 캐싱 대상이 아닙니다. 즉, 파싱은 하되 실행 계획을 재사용하거나 캐싱하지 않습니다. 하지만 `SQL Injection` 과 같이 보안 문제를 예방하기 위해서라도 PreparedStatement를 사용하는 것이 좋다고 생각합니다.