데이터 접근 기술을 고를 때 'JPA가 최신이니 전부 바꾸자'와 'SQL이 보이지 않으니 불안하다' 사이에서 멈추기 쉽다. 둘은 같은 문제를 다른 높이에서 다룬다. 여기서는 ACTIVE 상태의 회원을 조회하는 같은 요구를 두 방식으로 비교한다.
JdbcTemplate는 SQL을 직접 쓰되 반복을 줄인다
JDBC를 직접 쓰면 연결·예외 변환·자원 해제를 매번 처리해야 한다. JdbcTemplate는 이 반복을 맡기고 SQL과 결과 매핑은 코드에 남긴다.
List<Member> members = jdbcTemplate.query(
"select id, name from member where status = ?",
(rs, rowNum) -> new Member(rs.getLong("id"), rs.getString("name")),
"ACTIVE"
);결과는 ACTIVE 회원 목록이다. SQL이 눈앞에 있으므로 특정 조인, 집계, 데이터베이스 기능을 정밀하게 다뤄야 할 때 읽기 쉽다. 값은 문자열 연결이 아니라 ? 바인딩으로 전달해야 SQL 인젝션 위험을 줄일 수 있다.
JPA는 객체 상태와 관계를 중심으로 다룬다
Spring Data JPA에서는 같은 조회 요구를 리포지토리 메서드로 표현할 수 있다.
interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByStatus(String status);
}
List<Member> members = repository.findByStatus("ACTIVE");Member가 status 속성을 가진 엔티티이고 리포지토리가 등록돼 있다는 전제다. 두 방식 모두 같은 상태의 회원을 찾지만, JPA 쪽은 실제 SQL 생성과 객체 매핑을 프레임워크에 맡긴다. 편리하다고 실제로 어떤 SQL이 나가는지 보지 않아도 된다는 뜻은 아니다. 연관 관계를 무심코 순회하면 조회가 여러 번 발생하는 N+1 문제가 생길 수 있다. 엔티티 상태를 변경하는 작업이라면 영속성 컨텍스트와 트랜잭션 안에서 변경 감지가 일어나는지도 이해해야 한다.
기술을 섞을 때는 트랜잭션 경계를 먼저 정한다
한 서비스 메서드에서 JPA와 JdbcTemplate를 함께 쓸 수도 있다. 다만 둘이 같은 데이터 소스와 트랜잭션에 참여하는지 확인하지 않으면 한쪽만 반영되는 난처한 결과가 생길 수 있다. 변경 작업의 시작과 끝을 서비스 계층의 @Transactional로 분명히 하고, 실패 시 어떤 변경을 되돌릴지 테스트한다.
읽기 전용 목록은 SQL이 더 명확할 수 있고, 도메인 규칙과 연관 관계 변경은 JPA가 자연스러울 수 있다. 하나를 표준으로 삼더라도 예외가 필요한 이유를 문서와 코드 리뷰에서 남기면 혼합이 기술 부채로 번지는 것을 막을 수 있다.
같은 테스트 데이터에 ACTIVE 두 건과 다른 상태 한 건을 넣어 두 방식의 결과 ID가 일치하는지 확인해 보자. 결과가 다르면 매핑과 필터 조건부터, 결과는 같지만 느리다면 실행 SQL의 수와 계획부터 본다. 조회 결과 하나만으로 기술의 성능 우위를 판단하지 않는다.
성능은 추측 대신 쿼리와 실행 계획으로 본다
JPA를 쓴다고 데이터베이스가 빨라지거나 느려지지 않는다. 응답 시간이 문제라면 로그에서 실행 SQL과 바인딩 수를 확인하고, 데이터베이스의 실행 계획으로 인덱스·조인·정렬 비용을 본다. 대량 변경을 엔티티 하나씩 로드해 처리하면 메모리와 쿼리 수가 커질 수 있으므로, 명시적인 벌크 SQL이 더 나은 경우도 있다.
핵심 요약
JdbcTemplate는 SQL 제어권을 유지하면서 JDBC 반복을 줄이고, JPA는 객체 상태와 관계를 중심으로 개발을 돕는다. 중요한 기준은 유행이 아니라 쿼리의 성격, 트랜잭션 경계, 성능을 관찰할 방법이다. 어떤 선택이든 파라미터 바인딩과 실제 SQL 확인을 놓치지 말자.
작성자
기초 개념을 구현과 검증, 실제 운영 판단까지 연결해 기록합니다.

