PreparedStatement : 개념, 장점, 그리고 고급 활용 전략

Jayson·2025년 6월 16일
0
post-thumbnail

Spring JDBC 을 학습하다가 PreparedStatement에 대해서 알게되었다. 하지만 이에 대해서 깊이 알고 있지는 않아서 이번 기회에 학습을 진행하였습니다.

PreparedStatement, 너 도대체 누구니? (개발자라면 꼭 알아야 할 이야기)

안녕하세요! 오늘은 우리가 서버 개발을 하면서 데이터베이스(DB)와 소통할 때 정말 정말 중요한 친구, PreparedStatement에 대해 알아보려고 해요. 아마 프로젝트를 하다 보면 Statement라는 것도 있고, PreparedStatement라는 것도 있어서 "둘 다 쿼리 날리는 거 아닌가? 뭐가 다르지?" 하고 궁금했던 적, 다들 한 번쯤 있으실 거예요.

오늘 이 글을 다 읽고 나면, 왜 다들 PreparedStatement를 써야 한다고 입이 닳도록 말하는지, 그리고 이걸 쓰면 얼마나 삶의 질이 올라가는지 확실히 알게 되실 겁니다! 😉

1. PreparedStatement의 기본 컨셉: "미리 준비해서 똑똑하게 일하기"

PreparedStatement를 한마디로 표현하면 "미리 준비된 SQL 실행 전문가"라고 할 수 있어요.

우리가 요리를 할 때, 매번 처음부터 레시피를 정독하고 재료를 다듬는 것보다, 레시피의 큰 틀(예: 볶음밥 만들기)은 한번 익혀두고 그때그때 재료(김치, 새우, 베이컨 등)만 바꿔 넣는 게 훨씬 효율적이겠죠? PreparedStatement가 바로 그런 친구예요.

  • Statement의 방식:
    "어, SELECT * FROM users WHERE id = 'user1' 쿼리 실행해야지!" -> (DB: "음... 이 쿼리 처음 보네. 문법 맞나? 어떻게 실행하는 게 제일 빠르지? 분석 끝! 실행!")
    "어, SELECT * FROM users WHERE id = 'user2' 쿼리 실행해야지!" -> (DB: "오잉, 이것도 처음 보는 쿼리네. 또 분석해야겠다. 분석 끝! 실행!")

  • PreparedStatement의 방식:

    1. 일단 레시피(SQL 템플릿)부터 알려줘요.
      "안녕 DB! 앞으로 SELECT * FROM users WHERE id = ? 이런 모양의 쿼리를 계속 쓸 거야. 미리 준비 좀 해둘래?"
    2. DB는 이 템플릿을 받아서 딱 한 번만 분석하고 최적의 실행 계획을 세워둬요 (캐싱).
      (DB: "오케이! 이 쿼리 구조는 이제 외웠어. 실행 계획 저장 완료!")
    3. 그다음부터는 재료(값)만 쏙쏙 바꿔서 요청해요.
      "아까 그 쿼리, ? 자리에 'user1' 넣어서 실행해줘!"
      "이번엔 'user2' 넣어서!"
      (DB: "응, 아까 외운 대로 바로 실행할게! 분석 과정은 생략! 완전 빠르지?")

이렇게 미리 컴파일을 해두기 때문에, 같은 구조의 쿼리를 반복해서 실행할 때 속도 차이가 어마어마하게 난답니다.

잠깐! 그럼 테이블 이름이나 컬럼 이름도 ?로 바꿀 수 있나요?
아쉽지만 그건 안돼요! ?는 데이터가 들어갈 자리를 의미하는 거라, 테이블이나 컬럼 이름 같은 SQL의 '구조' 자체를 바꾸는 데는 쓸 수 없답니다. 이런 걸 동적으로 바꾸고 싶을 땐, 허용된 이름 목록(화이트리스트)을 만들어두고 그 안의 값일 때만 쿼리를 조합하는 식으로 안전장치를 꼭 마련해야 해요!


2. PreparedStatement를 쓰면 좋은 점 3가지 (진짜 중요!)

(1) 성능이 날아올라요! (feat. 배치 작업)

위에서 말한 '사전 컴파일'과 '캐싱' 덕분에 성능이 정말 좋아져요. 특히 여러 데이터를 한 번에 넣거나 수정하는 배치(Batch) 작업을 할 때 그 효과는 극대화된답니다.

100개의 데이터를 INSERT 해야 할 때, Statement는 DB에 100번 왔다 갔다 해야 하지만, PreparedStatement는 "자, 여기 100개 데이터 묶음이야! 아까 알려준 그 방식대로 한 방에 처리해줘!" 하고 요청할 수 있어요. 이걸 addBatch(), executeBatch() 메소드로 구현하는데, 네트워크 통신 횟수를 획기적으로 줄여주니 당연히 빠를 수밖에 없겠죠?

(2) 해킹 공격을 막아주는 든든한 방패! (SQL 인젝션 방어)

PreparedStatement를 써야 하는 가장 중요한 이유예요. SQL 인젝션은 해커가 입력값에 악의적인 SQL 코드를 심어서 DB를 공격하는 정말 무서운 해킹 기법인데요.

예를 들어 Statement로 로그인 폼을 만들면 이렇게 되겠죠?
String query = "SELECT * FROM users WHERE id = '" + userId + "' AND pw = '" + userPw + "'";

만약 해커가 userId' or '1'='1 같은 값을 입력하면... 쿼리는 ... WHERE id = '' or '1'='1' AND ...가 되면서 아이디, 비번 없이 로그인이 돼버리는 대참사가 발생해요!

하지만 PreparedStatement는 달라요.
String query = "SELECT * FROM users WHERE id = ? AND pw = ?";

PreparedStatement?에 들어오는 값을 절대 SQL 코드로 보지 않고, 순수한 데이터 값으로만 취급해요. 해커가 뭘 입력하든 "응, 너는 그냥 '' or '1'='1'이라는 이름의 아이디를 가진 사용자를 찾는 거구나?" 하고 순진하게(?) 받아들이기 때문에, 해킹이 원천적으로 차단된답니다. 정말 든든하죠?

(3) 코드가 예뻐져요! (가독성 & 유지보수)

Statement를 쓰면 + 연산자로 변수랑 SQL을 덕지덕지 이어 붙여야 해서, 코드가 길어지면 "이게 대체 뭔 소리야..." 싶은 '스파게티 코드'가 되기 쉬워요. 작은따옴표(') 하나 빼먹어서 에러 나는 건 덤이고요.

하지만 PreparedStatement는 어떤가요? SQL 템플릿은 그 자체로 깔끔하고, ?에 어떤 값이 들어가는지는 pstmt.setString(1, ...) 처럼 순서대로 착착 정리되니 코드를 읽기도, 나중에 고치기도 훨씬 편하답니다.


3. 실전에서는 어떻게 쓰일까? (feat. MyBatis, JPA)

사실 요즘은 우리가 직접 JDBC 코드를 짜는 일은 많지 않아요. 대신 MyBatis나 JPA 같은 멋진 프레임워크를 쓰죠. 재밌는 건, 이 프레임워크들도 내부적으로는 PreparedStatement의 똑똑한 원리를 그대로 사용하고 있다는 거예요!

  • MyBatis: XML에 쿼리를 짤 때 쓰는 #{id} 이게 바로 PreparedStatement?로 변환되는 친구랍니다. 그래서 안전하죠! 반면 ${id}는 문자열을 그냥 갖다 붙이는 거라 SQL 인젝션에 취약하니 정말 특별한 경우가 아니면 쓰면 안 돼요!
  • JPA (Hibernate): 우리가 userRepository.save(user) 같은 코드를 짜면, JPA가 알아서 똑똑하게 INSERTUPDATE 쿼리를 PreparedStatement를 사용해서 만들어준답니다. 개발자는 SQL 걱정 없이 객체지향적으로 코드에만 집중할 수 있게 되는 거죠.

꿀팁! 커넥션 풀과 함께 쓸 때
보통 서버는 DB 커넥션을 미리 여러 개 만들어두고 돌려쓰는 '커넥션 풀'을 사용하는데요, PreparedStatement도 이 커넥션 풀이 캐싱해주는 기능이 있답니다. 덕분에 우리는 그냥 쓰기만 해도 성능 이점을 누릴 수 있어요!


4. 마무리하며: 그래서 결론은?

자, 정리해볼까요?

PreparedStatement는...
1. 빠르다! (한 번만 컴파일하고 재사용하니까)
2. 안전하다! (SQL 인젝션을 막아주니까)
3. 깔끔하다! (코드가 보기 좋고 관리하기 편하니까)

이 정도면 Statement 대신 PreparedStatement를 써야 할 이유는 충분하겠죠?

이제부터 여러분이 DB와 관련된 코드를 짤 때는 "아, 이건 당연히 PreparedStatement로 짜야지!" 하는 생각이 자연스럽게 드실 거예요. 성능, 보안, 가독성이라는 세 마리 토끼를 모두 잡는 PreparedStatement, 우리 모두의 프로젝트를 위해 꼭 기억하고 사용하자고요! 😊

profile
Small Big Cycle

0개의 댓글