노트

JDBC와 JdbcTemplate

JDBC (Java Database Connectivity) and JdbcTemplate

백엔드#spring#db · 연결된 개념 7개

쉽게 말하면

JdbcTemplate은 DB에 쿼리할 때마다 연결 열기, 결과 넘겨보기, 예외 처리, 뒷정리 같은 반복 일을 대신해 주는 집사 같아요. 나는 어떤 SQL을 보낼지와 결과 한 줄을 어떤 객체로 받을지만 정하면 되죠.

비유의 한계 집사가 SQL까지 대신 짜 주진 않아요. SQL은 여전히 직접 쓰고, 값은 문자열로 이어 붙이지 말고 파라미터로 바인딩해야 SQL 주입을 막아요. 객체로 다루는 다음 단계가 JPA예요.

JDBC(Java Database Connectivity)는 자바가 여러 DB와 통신하기 위한 표준 API이고, Hibernate·MyBatis·스프링 JDBC 모두 그 위에 있다. JdbcTemplate은 스프링이 JDBC의 반복 작업(연결 열고 닫기, 예외 변환, 결과 순회)을 대신해 주는 클래스다.

순수 JDBC가 번거로운 이유

드라이버 로드 → Connection 얻기 → Statement 준비 → 실행
→ ResultSet 순회 → 예외 처리 → ResultSet·Statement·Connection 닫기

매 쿼리마다 이 보일러플레이트를 쓰고, 체크 예외(checked exception)를 직접 처리하고, 닫는 것을 깜빡하면 연결이 샌다. SQL에 DB 방언(dialect)이 섞이면(MySQL LIMIT vs Oracle FETCH FIRST) DB를 바꿀 때 전부 고쳐야 한다.

JdbcTemplate이 맡는 것

개발자는 SQL, 파라미터, 행 하나를 객체로 바꾸는 방법만 정한다. 연결 관리, 실행, 순회, 예외 변환(DataAccessException), 트랜잭션 참여, 자원 정리는 스프링이 한다. 아래 query(sql, rowMapper, args...)에서 람다 (rs, rowNum) -> ...가 RowMapper이고, "OPEN"이 ? 자리에 들어간다.

public List<Contact> findOpen() {
    return jdbc.query("SELECT id, name, status FROM contact WHERE status = ?",
        (rs, rowNum) -> new Contact(rs.getLong("id"), rs.getString("name"), rs.getString("status")),
        "OPEN");
}
 
int updated = named.update(
    "UPDATE contact SET status = :status WHERE id = :id",
    Map.of("status", "CLOSED", "id", id));
  • RowMapper: ResultSet의 한 행을 객체로 바꾸는 함수. 컬럼명과 필드명이 같으면 BeanPropertyRowMapper나 DataClassRowMapper를 쓴다
  • NamedParameterJdbcTemplate: ? 대신 :이름 자리표시자. 파라미터가 많을 때 순서 실수를 막는다
  • 파라미터 바인딩은 SQL 주입(SQL injection)을 막는다. 문자열 결합으로 SQL을 만들지 않는다
  • 스레드 안전해서 빈 하나를 여러 DAO(Data Access Object)가 공유한다. 스프링 부트는 DataSource와 함께 자동으로 만든다
  • 스프링 6.1부터는 플루언트 API인 JdbcClient도 있다(2026 기준)

H2

자바로 만든 인메모리 DB라 테스트·PoC(Proof of Concept)에 쓴다. 스프링 부트는 클래스패스에 H2가 있으면 자동으로 연결하고, schema.sql·data.sql로 시작 시 테이블과 데이터를 만든다. 재시작하면 데이터가 사라지고 방언이 운영 DB와 달라, 운영과 같은 DB를 컨테이너로 띄우는 테스트(Testcontainers)가 더 믿을 만하다.

H2 웹 콘솔 (Spring Boot 3.5 기준)

브라우저에서 테이블을 보는 콘솔이다. DevTools를 쓰면 자동으로 켜지고, 아니면 spring.h2.console.enabled=true로 켠다. 경로는 기본 /h2-console이고 spring.h2.console.path로 바꾼다. 스프링 부트 4부터는 spring-boot-h2console 의존성도 있어야 한다(2026 기준).

spring.h2.console.enabled=true
# 기본값 true면 DB 이름이 실행마다 무작위로 정해진다
spring.datasource.generate-unique-name=false
# 부트가 만드는 URL: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.name=testdb
  • 계정을 따로 정하지 않으면 부트는 내장 DB에 사용자 sa, 빈 비밀번호를 쓴다
  • 부트는 기본으로 DB 이름을 무작위로 만든다. 콘솔에 넣을 JDBC URL을 고정하려면 위처럼 generate-unique-name=false로 끈다. 이때 이름을 안 주면 testdb가 된다

스프링 시큐리티를 쓰면 콘솔용 설정을 더 해야 한다. 콘솔은 프레임으로 화면을 그리고 CSRF 방어를 하지 않기 때문이다. 콘솔 요청에만 CSRF 검사를 끄고, 응답의 X-Frame-Options를 SAMEORIGIN으로 둬야 한다. 부트 문서는 콘솔 경로 전용 필터 체인을 따로 두는 방법을 보여 준다. PathRequest.toH2Console()은 경로를 바꿔도 맞는 경로를 가리킨다.

@Profile("dev")
@Configuration(proxyBeanMethods = false)
public class DevH2ConsoleSecurityConfig {
 
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    SecurityFilterChain h2ConsoleSecurityFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher(PathRequest.toH2Console());
        // 부트 문서는 이 자리를 yourCustomAuthorization()으로 비워 둔다. 여기서는 개발용이라 모두 허용했다
        http.authorizeHttpRequests((auth) -> auth.anyRequest().permitAll());
        http.csrf(CsrfConfigurer::disable);
        http.headers((headers) -> headers.frameOptions(FrameOptionsConfig::sameOrigin));
        return http.build();
    }
}

콘솔은 개발용이다. 운영에서 CSRF 방어를 끄거나 프레임을 허용하면 보안 위험이 생기므로 @Profile("dev")처럼 개발 환경에만 둔다.

SQL을 직접 쓰지 않고 객체로 다루는 다음 단계는 Spring Data 리포지터리와 쿼리 메서드와 JPA다. 클래스를 테이블에 대응시키는 방법은 JPA 엔티티 기본 매핑에 있다. 오라클 저장 프로시저 호출은 Oracle PL/SQL을 본다.

출처: Spring Framework 문서: JdbcTemplate · NamedParameterJdbcTemplate · JdbcClient · Spring Boot 3.5 문서: H2 웹 콘솔 · 보안 애플리케이션에서 H2 콘솔 쓰기 · spring.datasource.generate-unique-name · DataSourceProperties 소스: 기본 계정 · EmbeddedDatabaseConnection 소스: H2 URL · Spring Boot 4 문서: H2 웹 콘솔 · X-Frame-Options

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • Lombok

    Lombok은 게터·세터·생성자·toString 같은 반복 코드를 어노테이션으로 대신 생성해 주는 자바 라이브러리다. 컴파일할 때 어노테이션 처리기(annotation processor)가 바이트코드(bytecode)에 메서드를 넣는다. 스프링과는 별개 라이브러리라 따로 추가한다.

  • 스프링 REST 컨트롤러

    @RestController는 메서드의 반환값을 뷰 이름이 아니라 HTTP 응답 본문(보통 JSON)으로 쓰는 컨트롤러다. @Controller에 @ResponseBody를 합친 것으로, 직렬화(serialization)는 Jackson이 맡는다.

  • 스프링 부트 자동 설정

    스프링 부트는 스프링 앱을 만들 때 반복되던 설정(의존성 버전 맞추기, 서블릿 컨테이너 연결, 배포)을 관례(convention over configuration)로 대신해 주는 도구다. 클래스패스(classpath)에 무엇이 있는지 보고 필요한 빈을 자동으로 등록하고, 웹 서버를 내장해 java -jar 하나로 실행된다.

  • 의존성 주입 (DI)

    객체가 필요한 협력 객체를 직접 new로 만들지 않고, 바깥(스프링 컨테이너)에서 넣어 받는 방식. 무엇을 언제 만들고 어떻게 연결할지의 제어가 내 코드에서 컨테이너로 넘어가므로 제어의 역전(Inversion of Control, IoC)이라고도 부른다.

  • Bean Validation

    Bean Validation은 객체 필드에 제약 어노테이션(constraint annotation)을 붙여 입력을 검증하는 자바 표준이다(Jakarta Validation, 구현체는 Hibernate Validator). 스프링은 컨트롤러 파라미터에 @Valid가 붙으면 바인딩 직후 검증을 실행한다.

보기 옵션