1) PreparedStatment 자세히 알아보기
(1) 개요
- Statment 종류에는 Statement, PreparedStatement, CallableStatement 3가지가 존재한다.
- CallableStatement는 PL/SQL문을 호출할 때 사용한다고 했지만 성능상 이슈로 인해 거의 사용하지 않기에 Statement나 PreparedStatement를 사용해야 한다.
- 그런데 실무 환경에서는 Statement는 쓰지 않고 PreparedStatement만 사용하는데 그 이유를 알아 보자!
(2) Statement, PreparedStatement 동작 방식
- Statement 및 PreparedStatement 방식은 아래와 같은 공통적인 실행 과정을 거치게 된다.
① 구문 분석 (Parsing) 및 정규화 (Normalization)
- Query 문법 확인 및 데이터베이스, 테이블 존재여부 확인
② 컴파일 (Compliation)
- Query 컴파일
③ Query 최적화 (Query Optimization Phase)
- Query 실행 방법의 최적 계획 선택
④ 캐시 (Cache)
- Query 최적화 단계에서 선택된 계획이 캐시에 저장되어 동일한 Query 실행 시, 1 ~ 3단계를 실행하지 않고 캐시를 통해 찾음
⑤ 실행 (Execution Phase)
- Query가 실행된 값이 담긴 객체 (ResultSet)를 사용자에게 반환
(3) Statement vs PreparedStatement 차이점
캐시 사용 유무 (재사용성)
- Statement는 매번 Query 실행 시 마다, 1번에서 5번까지의 과정을 반복하며, Prepared Statement는 최초 Query 실행 시, 1번에서 5번까지의 과정을 반복하지만 두 번째 Query 실행 부터는 1번부터 3번까지의 과정을 생략하고 4번부터 시작한다.
- 그런데 Statement 방식도 Prepared Statement와 같이 캐시를 사용하는데 어떠한 차이점이 있는지 코드로 알아 보자!
[Statement]
Connection conn = DriverManager.getConnection(url, id, pwd);
Statement stmt = conn.createStatement();
stmt.executeUpdate("Update Member Set Name = " + "머스크햄" Where id = " + 10);
stmt.executeUpdate("Update Member Set Name = " + "화성갈끄니까" Where id = " + 11;
[PreparedStatement]
Connection conn = DriverManager.getConnection(url, id, pwd);
String sql = "Update Member Set Name = ? Where id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "머스크햄");
pstmt.setInt(2, 10);
pstmt.executeUpdate();
pstmt.setString(1, "화성 갈끄니까");
pstmt.setInt(2, 11);
pstmt.executeUpdate();
- Statement, PreparedStatement 방식 둘 다 Query를 컴파일하고 최적화한 다음 실행되기 전에 Caching 되지만 Statement는 첫 수행한 Query와 완전히 일치하는 Query를 요청하는 경우에만 캐싱한 데이터를 재활용할 수 있다.
- 즉, 위처럼 첫 번째 Query와 일치하지 않는 별도의 Query를 실행하게 되면 컴파일을 다시 해야 한다.
- 그에 비해, Prepared Statement 방식은 Statement 방식과는 달리 placeHolder (?)를 이용하여 파라미터를 바인딩하기 때문에 기존에 컴파일된 Query를 재활용할 수 있다.
- 따라서, Prepared Statement 방식을 사용하면 DB의 부하를 낮추고 속도를 더 높일 수 있는 것이다.
- 또한, 파라미터를 Query문 안에 직접 넣어주는 Statment 방식은 가독성도 좋지 못하고 코딩하기에도 번거롭지만 PreparedStatement 방식은 파라미터 바인딩을 통해 가독성도 챙기고 손쉽게 코딩할 수 있는 장점이 있다.
SQL Injection 방지 (보안성)
- Prepared Statement 방식을 사용하면 SQL Injection 공격 또한 방지할 수 있는데 코드를 통해 알아보도록 하자!
[기존 Statement 방식]
Connection conn = DriverManager.getConnection(url, id, pwd);
Statement stmt = conn.createStatement();
stmt.executeQuery("Select * from Member Where id = " + id + " and pwd = " + pwd);
→ 로그인 시, 사용자에게서 입력받은 값을 Where 조건절에 넣어주어 조회하는 Query이지만 만약, 다음과 같이 공격자가 파라미터 값을 악의적으로 조작해서 보낼 수도 있다.
[사용자에게서 입력받은 값]
id = test123
pwd = 1234’ or 1=1
Query = Select * from Member Where id = ‘test123’ and pwd = ‘1234’ OR ‘1’ = ‘1’;
→ 이처럼 공격자가 악의적으로 파라미터 값을 조작하여 “OR 1=1” 조건을 추가하는 방법으로 사용자 정보를 탈취할 수 있다.
[PreparedStatment 방식]
Connection conn = DriverManager.getConnection(url, id, pwd);
String sql = "Select * from Member Where id = ? and pwd = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, id);
pstmt.setString(2, pwd);
pstmt.executeQuery();
→ PreparedStatment 방식은 사용자에게서 받은 파라미터 값이 악의적으로 조작되었다 하더라도 파라미터 바인딩을 통해 SQL Injection을 방지할 수 있다.
→ 정확히는 pstmt.setString() 메소드 내부에서 사용되는 javaEncode() 메소드가 SQL Injection을 방지해주는데 내부 구현이 어떻게 되어있는지 살펴보자!
[javaEncode() 메소드]
public static void javaEncode(String s, StringBuilder buff, boolean forSQL) {
int length = s.length();
for (int i = 0, i < length; i++) {
char c = s.charAt(i);
switch (c) {
case ‘\t’:
buff.append(“\\t”);
break;
case ‘\n’:
buff.append(“\\n”);
break;
case ‘\f’:
buff.append(“\\f”);
break;
case ‘“’;
buff.append(‘\’’);
break;
...
}
}
}
→ 이처럼 공격자가 악의적으로 조작한 파라미터 값에 “\”를 붙여줌으로써 SQL Injection을 방지할 수 있는 것이다.
[Reference]
- https://go0g.org/66
- https://velog.io/@ragnarok_code/DataBase-Statement%EC%99%80-Prepared-Statement-%EC%B0%A8%EC%9D%B4%EC%A0%90
- https://devbox.tistory.com/entry/Comporison#:~:text=Statement%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B4%20%EB%A7%A4%EB%B2%88,%EB%8B%B4%EC%95%84%20%EC%9E%AC%EC%82%AC%EC%9A%A9%EC%9D%84%20%ED%95%9C%EB%8B%A4%EB%8A%94%20%EA%B2%83%EC%9D%B4%EB%8B%A4
- https://ksg97031.medium.com/prepared-statement%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-sqli%EB%A5%BC-%EB%B3%B4%ED%98%B8%ED%95%98%EB%8A%94%EA%B0%80-b55bd3012ec3
- https://javabypatel.blogspot.com/2017/06/why-prepared-statement-is-faster-than-statement-in-java.html
- https://hpjang.tistory.com/3
반응형
'👨💻 Back End > JDBC' 카테고리의 다른 글
Synchrozied 키워드를 이용한 동시성 이슈 해결 (0) | 2023.08.28 |
---|---|
커넥션 풀이란 (Connection Pool) (0) | 2023.08.28 |
JDBC에서 자주 사용되는 메소드 정리 (0) | 2023.08.26 |
JDBC를 이용한 CRUD 실습 (0) | 2023.08.26 |
Java에서 DB 연동을 위한 JDBC에 대해 알아보자! (0) | 2023.08.26 |