
Spring JDBC 을 학습하다가 PreparedStatement에 대해서 알게되었다. 하지만 이에 대해서 깊이 알고 있지는 않아서 이번 기회에 학습을 진행하였습니다.
안녕하세요! 오늘은 우리가 서버 개발을 하면서 데이터베이스(DB)와 소통할 때 정말 정말 중요한 친구, PreparedStatement에 대해 알아보려고 해요. 아마 프로젝트를 하다 보면 Statement라는 것도 있고, PreparedStatement라는 것도 있어서 "둘 다 쿼리 날리는 거 아닌가? 뭐가 다르지?" 하고 궁금했던 적, 다들 한 번쯤 있으실 거예요.
오늘 이 글을 다 읽고 나면, 왜 다들 PreparedStatement를 써야 한다고 입이 닳도록 말하는지, 그리고 이걸 쓰면 얼마나 삶의 질이 올라가는지 확실히 알게 되실 겁니다! 😉
PreparedStatement를 한마디로 표현하면 "미리 준비된 SQL 실행 전문가"라고 할 수 있어요.
우리가 요리를 할 때, 매번 처음부터 레시피를 정독하고 재료를 다듬는 것보다, 레시피의 큰 틀(예: 볶음밥 만들기)은 한번 익혀두고 그때그때 재료(김치, 새우, 베이컨 등)만 바꿔 넣는 게 훨씬 효율적이겠죠? PreparedStatement가 바로 그런 친구예요.
Statement의 방식:
"어, SELECT * FROM users WHERE id = 'user1' 쿼리 실행해야지!" -> (DB: "음... 이 쿼리 처음 보네. 문법 맞나? 어떻게 실행하는 게 제일 빠르지? 분석 끝! 실행!")
"어, SELECT * FROM users WHERE id = 'user2' 쿼리 실행해야지!" -> (DB: "오잉, 이것도 처음 보는 쿼리네. 또 분석해야겠다. 분석 끝! 실행!")
PreparedStatement의 방식:
SELECT * FROM users WHERE id = ? 이런 모양의 쿼리를 계속 쓸 거야. 미리 준비 좀 해둘래?"? 자리에 'user1' 넣어서 실행해줘!"이렇게 미리 컴파일을 해두기 때문에, 같은 구조의 쿼리를 반복해서 실행할 때 속도 차이가 어마어마하게 난답니다.
잠깐! 그럼 테이블 이름이나 컬럼 이름도
?로 바꿀 수 있나요?
아쉽지만 그건 안돼요!?는 데이터가 들어갈 자리를 의미하는 거라, 테이블이나 컬럼 이름 같은 SQL의 '구조' 자체를 바꾸는 데는 쓸 수 없답니다. 이런 걸 동적으로 바꾸고 싶을 땐, 허용된 이름 목록(화이트리스트)을 만들어두고 그 안의 값일 때만 쿼리를 조합하는 식으로 안전장치를 꼭 마련해야 해요!
위에서 말한 '사전 컴파일'과 '캐싱' 덕분에 성능이 정말 좋아져요. 특히 여러 데이터를 한 번에 넣거나 수정하는 배치(Batch) 작업을 할 때 그 효과는 극대화된답니다.
100개의 데이터를 INSERT 해야 할 때, Statement는 DB에 100번 왔다 갔다 해야 하지만, PreparedStatement는 "자, 여기 100개 데이터 묶음이야! 아까 알려준 그 방식대로 한 방에 처리해줘!" 하고 요청할 수 있어요. 이걸 addBatch(), executeBatch() 메소드로 구현하는데, 네트워크 통신 횟수를 획기적으로 줄여주니 당연히 빠를 수밖에 없겠죠?
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'이라는 이름의 아이디를 가진 사용자를 찾는 거구나?" 하고 순진하게(?) 받아들이기 때문에, 해킹이 원천적으로 차단된답니다. 정말 든든하죠?
Statement를 쓰면 + 연산자로 변수랑 SQL을 덕지덕지 이어 붙여야 해서, 코드가 길어지면 "이게 대체 뭔 소리야..." 싶은 '스파게티 코드'가 되기 쉬워요. 작은따옴표(') 하나 빼먹어서 에러 나는 건 덤이고요.
하지만 PreparedStatement는 어떤가요? SQL 템플릿은 그 자체로 깔끔하고, ?에 어떤 값이 들어가는지는 pstmt.setString(1, ...) 처럼 순서대로 착착 정리되니 코드를 읽기도, 나중에 고치기도 훨씬 편하답니다.
사실 요즘은 우리가 직접 JDBC 코드를 짜는 일은 많지 않아요. 대신 MyBatis나 JPA 같은 멋진 프레임워크를 쓰죠. 재밌는 건, 이 프레임워크들도 내부적으로는 PreparedStatement의 똑똑한 원리를 그대로 사용하고 있다는 거예요!
#{id} 이게 바로 PreparedStatement의 ?로 변환되는 친구랍니다. 그래서 안전하죠! 반면 ${id}는 문자열을 그냥 갖다 붙이는 거라 SQL 인젝션에 취약하니 정말 특별한 경우가 아니면 쓰면 안 돼요!userRepository.save(user) 같은 코드를 짜면, JPA가 알아서 똑똑하게 INSERT나 UPDATE 쿼리를 PreparedStatement를 사용해서 만들어준답니다. 개발자는 SQL 걱정 없이 객체지향적으로 코드에만 집중할 수 있게 되는 거죠.꿀팁! 커넥션 풀과 함께 쓸 때
보통 서버는 DB 커넥션을 미리 여러 개 만들어두고 돌려쓰는 '커넥션 풀'을 사용하는데요,PreparedStatement도 이 커넥션 풀이 캐싱해주는 기능이 있답니다. 덕분에 우리는 그냥 쓰기만 해도 성능 이점을 누릴 수 있어요!
자, 정리해볼까요?
PreparedStatement는...
1. 빠르다! (한 번만 컴파일하고 재사용하니까)
2. 안전하다! (SQL 인젝션을 막아주니까)
3. 깔끔하다! (코드가 보기 좋고 관리하기 편하니까)
이 정도면 Statement 대신 PreparedStatement를 써야 할 이유는 충분하겠죠?
이제부터 여러분이 DB와 관련된 코드를 짤 때는 "아, 이건 당연히 PreparedStatement로 짜야지!" 하는 생각이 자연스럽게 드실 거예요. 성능, 보안, 가독성이라는 세 마리 토끼를 모두 잡는 PreparedStatement, 우리 모두의 프로젝트를 위해 꼭 기억하고 사용하자고요! 😊