리타의 저장소

JAVA | JPA와 Mybatis에서의 PreparedStatement 동작방식 본문

Dev/Backend

JAVA | JPA와 Mybatis에서의 PreparedStatement 동작방식

ريتا 2025. 10. 11. 23:36

Java

 


같이 스터디를 하는 스터디원들과 PreparedStatement에 대해서 이야기를 하다가, 실제 담당하고 있는 서비스에서 PreparedStatement를 사용하냐고 물었을 때 다들 사용하지 않는다고 했다. 

대체로 JPA 혹은 Mybatis를 사용중이라 업무를 하면서 마주할일이 거의 없다는 것이었다. 근데, 또 여기서 궁금한게 생겼다. 과연 진짜 그들은 PreparedStatement를 사용하지 않는 것일까?

 


 

핵심부터 말하자면

JPA, MyBatis 모두 실제 SQL 실행할 때 JDBC API를 쓰고, 그 안에서 PreparedStatement를 사용한다.

즉, 직접 pstmt.setInt(1, xxx) 라고 쓰지 않아도 프레임워크가 내부에서 알아서 PreparedStatement를 만들고 파라미터를 바인딩해서 실행한다는 것이다.

 

* JDBC API란 ? Java에서 데이터베이스와 연결하고 SQL을 실행할 수 있게 해주는 표준 인터페이스.

JPA에서의 PreparedStatement 동작 방식

  • JPA (Hibernate 기준):
  1. EntityManager.find() 같은 메서드를 호출하거나,
  2. JPQL, CriteriaQuery, @Query 같은 걸 쓰면,
  3. Hibernate가 SQL을 생성하고,
  4. JDBC Connection을 열고,
  5. 그 위에 PreparedStatement를 생성해서,
  6. 바인딩 (setInt(), setString() 등)하고,
  7. executeQuery() 로 실행.

실제 내부 예시 

Hibernate 내부 예시 :

PreparedStatement pstmt = connection.prepareStatement("SELECT * FROM users WHERE id = ?");
pstmt.setInt(1, 10);
ResultSet rs = pstmt.executeQuery();

➡️ 실제 코드의 경우 find(User.class, id) 한 줄 썼지만,

➡️ Hibernate는 내부에서 PreparedStatement를 만들어서 처리.


JPA에서의 코드 예시:

User user = entityManager.find(User.class, 10);
  • "find" 메서드 하나로 SQL 준비 + 파라미터 바인딩 + 실행 다 해줌
  • PreparedStatement로 처리되기 때문에, Injection 걱정 없음 

MyBatis에서는 어떻게 할까?

MyBatis는 SQL Mapper. 직접 SQL을 XML이나 어노테이션으로 적게 됨.

MyBatis Mapper 예시:

<select id="findUserById" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>

여기서 #{id} 를 보면,

  • #{} 표기는 "PreparedStatement의 ? "처럼 작동.
  • MyBatis는 이걸 자동으로 바인딩해줌.

내부 처리 흐름

MyBatis 내부 코드 예시:

PreparedStatement pstmt = connection.prepareStatement("SELECT * FROM users WHERE id = ?");
pstmt.setInt(1, id);
ResultSet rs = pstmt.executeQuery();

➡️ 즉, MyBatis도 무조건 PreparedStatement로 변환해서 실행.

* 참고 *

  • #{id} : 바인딩 (PreparedStatement, 안전함)
  • ${id} : 그냥 문자열 치환 (PreparedStatement 안씀, Injection 위험)

    MyBatis 쓸 때 #{id} 방식을 쓰는게 더 좋은 이유