이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
DataSource, Connection Pool,JdbcTemplate은 데이터 접근 흐름에서 각각 어떤 책임을 맡는가?- 트랜잭션과 커넥션은 어떤 방식으로 연결되는가?
- JPA를 쓰는 프로젝트에서도 JdbcTemplate을 알아야 하는 이유는 무엇인가?
개요
데이터 접근의 가장 아래에는 결국 Connection이 있다. JPA Repository를 쓰든 JdbcTemplate을 쓰든 애플리케이션은 데이터베이스와 통신하기 위해 Connection을 얻어 SQL을 실행한다. DataSource는 이 Connection을 얻는 표준 진입점이고, 커넥션 풀은 비싼 Connection 생성을 재사용으로 줄인다.
JdbcTemplate은 JDBC를 직접 사용할 때의 반복 코드를 제거한다. Connection 획득, Statement 준비, ResultSet 처리, 예외 변환, 리소스 정리를 Spring이 처리하고 개발자는 SQL과 매핑에 집중한다.
DataSource와 커넥션 풀
DataSource는 Connection을 만들어 주거나 풀에서 빌려 준다. Spring Boot는 JDBC 드라이버와 설정을 보고 DataSource를 자동 구성할 수 있다. 운영 환경에서는 보통 HikariCP 같은 커넥션 풀이 사용된다.
커넥션 풀은 성능을 높이는 도구이지만 병목을 없애는 마법은 아니다. 풀 크기를 너무 크게 잡으면 애플리케이션 대기는 줄어도 DB 서버가 감당해야 할 동시 작업이 늘어난다. 풀 크기를 너무 작게 잡으면 요청이 커넥션을 기다리다 timeout될 수 있다.
트랜잭션이 시작되면 Spring은 현재 실행 흐름에 Connection을 바인딩할 수 있다. 같은 트랜잭션 안의 여러 JDBC 작업이 같은 Connection을 사용해야 commit과 rollback이 의미 있기 때문이다. Spring JDBC는 이런 흐름을 위해 DataSourceUtils를 사용하므로, 트랜잭션이 필요한 코드에서 직접 dataSource.getConnection()을 호출하고 임의로 닫는 방식은 피해야 한다.
JdbcTemplate 원리
순수 JDBC는 다음 작업이 반복된다. Connection을 얻고, PreparedStatement를 만들고, 파라미터를 바인딩하고, ResultSet을 읽고, 예외를 처리하고, 모든 리소스를 닫아야 한다.
JdbcTemplate은 이 흐름을 템플릿으로 고정한다. 개발자는 SQL과 RowMapper를 제공하고, Spring은 나머지 반복 작업을 수행한다. 설정이 끝난 JdbcTemplate은 thread-safe하게 공유할 수 있으므로 Repository마다 같은 Bean을 주입받아 사용해도 된다.
@Repository
public class MemberJdbcRepository {
private final JdbcTemplate jdbcTemplate;
public MemberJdbcRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Optional<Member> findById(Long id) {
String sql = "select id, email, name from members where id = ?";
return jdbcTemplate.query(sql, memberRowMapper(), id).stream().findFirst();
}
private RowMapper<Member> memberRowMapper() {
return (rs, rowNum) -> new Member(
rs.getLong("id"),
rs.getString("email"),
rs.getString("name"));
}
}이 코드는 SQL이 분명하고, 결과 매핑이 명시적이다. JPA의 영속성 컨텍스트나 지연 로딩이 필요 없는 조회에서는 이런 단순함이 장점이다.
JPA와 함께 쓰는 기준
JPA를 사용하더라도 모든 조회를 Entity 중심으로 처리할 필요는 없다. 복잡한 리포트, 집계, 대량 조회, DB 전용 기능이 필요한 쿼리는 JdbcTemplate이 더 예측 가능할 수 있다. Spring 6.1 이후에는 JdbcClient처럼 JdbcTemplate 기반 기능을 더 fluent하게 쓰는 선택지도 있다.
중요한 것은 혼용 기준을 정하는 것이다. 도메인 변경과 aggregate 저장은 JPA가 맡고, 읽기 모델이나 성능 민감 조회는 JdbcTemplate이나 전용 query repository가 맡는 식으로 경계를 나누면 혼란이 줄어든다. 같은 트랜잭션 안에서 JPA와 JdbcTemplate을 섞는다면 같은 DataSource와 TransactionManager 흐름에 묶이는지 확인하고, JPA 변경 후 JdbcTemplate으로 즉시 조회해야 한다면 flush 타이밍도 함께 확인해야 한다.
실전 팁
- 커넥션 풀 문제를 만나면 풀 크기보다 먼저 느린 쿼리와 긴 트랜잭션을 확인한다.
- JdbcTemplate RowMapper는 재사용하되 과도한 추상화로 SQL 의도를 숨기지 않는다.
- JPA와 JdbcTemplate을 섞을 때 같은 DataSource와 트랜잭션 동기화 흐름에 묶이는지 확인한다.
- JPA 변경 직후 JdbcTemplate 조회 결과가 이상하면 flush가 실제로 일어났는지 확인한다.
- 집계와 목록 조회는 Entity 로딩보다 DTO projection이나 JdbcTemplate이 더 적합할 수 있다.
- 운영에서는 pool active, idle, pending, timeout, slow query 지표를 함께 본다.
위험 신호!
- DB 장애를 풀 크기 증가로만 해결하려 한다.
- 트랜잭션 안에서 외부 API 호출 때문에 커넥션이 오래 점유된다.
- JdbcTemplate SQL이 Service 계층 여기저기에 흩어져 있다.
- RowMapper가 도메인 규칙까지 수행한다.
- JPA flush 타이밍과 JdbcTemplate 조회 타이밍이 섞여 예상과 다른 데이터를 읽는다.
- 트랜잭션 안에서 직접 얻은 Connection을 임의로 닫아 Spring의 리소스 동기화를 깨뜨린다.
확인 질문
확인 질문
DataSource는 무엇을 제공하는가?
- 데이터베이스 Connection을 얻는 표준 진입점을 제공한다.
JdbcTemplate이 해결하는 핵심 반복 작업은 무엇인가?
- JDBC 리소스 관리, SQL 실행 흐름, 결과 매핑 연결, 예외 변환을 처리한다.
- JPA 프로젝트에서 JdbcTemplate을 함께 쓸 수 있는 대표 상황은 무엇인가?
- 복잡한 조회, 집계, 대량 처리, 성능 민감 SQL처럼 명시적 SQL 제어가 필요한 상황이다.