[Spring] JdbcTemplate

윤여송·2026년 9월 19일

Spring

목록 보기
4/12

JdbcTemplate

1. JDBC → JdbcTemplate

JDBC는 Java가 H2, MySQL 같은 데이터베이스와 통신하는 기본 API다.

순수 JDBC를 사용하면 연결을 얻고, SQL을 준비하고, 실행 결과를 객체로 바꾸고, 연결을 닫는 일을 직접 해야 한다. JdbcTemplate은 이 반복 작업을 대신 처리하고, 개발자는 SQL과 결과 변환에 집중하게 해준다.

MemberService
  ↓
JdbcTemplateMemberRepository
  ↓
JdbcTemplate
  ↓
DataSource (DB 연결 정보)
  ↓
H2 Database

2. DataSource는 무엇인가?

DataSource는 DB에 접속하기 위한 URL, 사용자명, 비밀번호 등의 연결 정보를 가진 객체다.

현재 프로젝트에서는 H2 드라이버와 JDBC/JPA 관련 의존성이 있으므로 Spring Boot가 DataSource Bean을 자동으로 만들어 준다.

application.yaml에 DB 정보를 지정하면 Spring Boot는 그 주소로 연결한다.

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/test
    username: sa
    password:
    driver-class-name: org.h2.Driver

위 설정은 H2 콘솔의 JDBC URL도 똑같이 jdbc:h2:tcp://localhost/~/test로 설정했을 때 같은 DB를 바라본다.

URL을 설정하지 않으면 Spring Boot는 보통 jdbc:h2:mem:... 형태의 임시 인메모리 DB를 자동 생성한다. 이 DB는 애플리케이션이 종료되면 사라지고, H2 콘솔에서 별도로 연 DB와 다를 수 있다.

3. JdbcTemplate Repository를 Bean으로 등록하기

현재 프로젝트는 컴포넌트 스캔이 아니라 SpringConfig에서 직접 Bean을 등록한다.

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService(MemberRepository memberRepository) {
        return new MemberService(memberRepository);
    }

    @Bean
    public MemberRepository memberRepository(DataSource dataSource) {
        return new JdbcTemplateMemberRepository(dataSource);
    }
}

스프링은 memberRepository() 메서드의 DataSource 파라미터에 자동으로 DataSource Bean을 넣는다. 그 결과 MemberService에는 JdbcTemplateMemberRepository가 MemberRepository 타입으로 주입된다.

이 방식에서는 Repository 클래스의 생성자에 @Autowired를 붙일 필요가 없다.

public JdbcTemplateMemberRepository(DataSource dataSource) {
    this.jdbcTemplate = new JdbcTemplate(dataSource);
}

생성자가 하나인 Bean은 @Autowired 없이도 생성자 주입이 가능하다. 특히 현재처럼 new JdbcTemplateMemberRepository(dataSource)로 직접 생성하는 경우, 생성자에 붙인 @Autowired는 의미가 없다.

4. RowMapper: DB 한 행을 Member 객체로 변환

DB 조회 결과는 ResultSet이라는 행 단위 데이터다. RowMapper는 ResultSet의 한 행을 Member 객체 하나로 바꾸는 함수다.

private RowMapper<Member> memberRowMapper() {
    return (rs, rowNum) -> {
        Member member = new Member();
        member.setId(rs.getLong("id"));
        member.setName(rs.getString("name"));
        return member;
    };
}

예를 들어 DB의 한 행이 아래와 같다면:

idname
1spring

RowMapper는 다음과 같은 Member 객체를 만든다.

Member member = new Member();
member.setId(1L);
member.setName("spring");

5. 조회: findById, findByName, findAll

@Override
public Optional<Member> findById(Long id) {
    List<Member> result = jdbcTemplate.query(
            "select * from member where id = ?",
            memberRowMapper(),
            id
    );
    return result.stream().findAny();
}

@Override
public Optional<Member> findByName(String name) {
    List<Member> result = jdbcTemplate.query(
            "select * from member where name = ?",
            memberRowMapper(),
            name
    );
    return result.stream().findAny();
}

@Override
public List<Member> findAll() {
    return jdbcTemplate.query("select * from member", memberRowMapper());
}

?는 반드시 값과 함께 전달한다

SQL의 ?는 파라미터 자리다.

select * from member where name = ?

따라서 JdbcTemplate 호출의 마지막 인자로 name을 넘겨야 한다.

jdbcTemplate.query(sql, memberRowMapper(), name);

값을 넘기지 않으면 아래처럼 오류가 발생한다.

Parameter "#1" is not set

#1은 첫 번째 ?에 값이 없다는 뜻이다.

6. 저장: SimpleJdbcInsert

SimpleJdbcInsert는 INSERT SQL을 직접 길게 쓰지 않고 저장하도록 도와준다.

@Override
public Member save(Member member) {
    SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate)
            .withTableName("member")
            .usingGeneratedKeyColumns("id");

    Map<String, Object> parameters = new HashMap<>();
    parameters.put("name", member.getName());

    Number key = jdbcInsert.executeAndReturnKey(
            new MapSqlParameterSource(parameters)
    );

    member.setId(key.longValue());
    return member;
}

동작 순서:

member.getName() 추출
  ↓
MEMBER 테이블에 INSERT 실행
  ↓
DB가 자동 생성한 ID 반환
  ↓
member.setId(...)로 Java 객체에도 ID 반영
  ↓
저장된 Member 반환

save()가 return null; 상태라면 Repository는 연결되어 있어도 INSERT SQL을 실행하지 않으므로 DB에 저장되지 않는다.

7. @Transactional 테스트와 기존 데이터

@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
}
  • @SpringBootTest: 실제 Spring Boot 컨테이너와 Bean을 띄워서 테스트한다.
  • @Transactional: 각 테스트에서 수행한 DB 변경을 테스트 종료 후 롤백한다.

하지만 @Transactional은 테스트 시작 전에 이미 DB에 있던 데이터를 지우지는 않는다.

예를 들어 H2 콘솔에서 이미 spring 회원을 넣어 두었다면, 테스트의 첫 가입 시도도 중복 회원 예외가 난다.

DELETE FROM MEMBER;

테스트 전에 데이터를 비우고 싶을 때는 위 SQL을 사용할 수 있다. ID 자동 증가값까지 초기화하려면:

TRUNCATE TABLE MEMBER RESTART IDENTITY;

실무에서는 애플리케이션용 DB와 테스트용 DB를 분리하는 것이 좋다. 그래야 콘솔에서 넣은 데이터 때문에 테스트 결과가 달라지지 않는다.

핵심 요약

  1. Spring Boot가 DataSource를 만들고, JdbcTemplate은 그 DataSource로 DB와 통신한다.
  2. SpringConfig에서 JdbcTemplateMemberRepository를 Bean으로 등록하면 서비스가 해당 구현체를 사용한다.
  3. RowMapper는 조회한 DB 행을 Java Member 객체로 변환한다.
  4. SQL의 ?에는 반드시 호출 시 값을 전달해야 한다.
  5. SimpleJdbcInsert는 INSERT 후 자동 생성된 ID를 받아 Member에 넣어 준다.
  6. @Transactional 테스트는 테스트 중 변경을 롤백하지만, 기존 DB 데이터까지 초기화하지는 않는다.
profile
y_some__velog

0개의 댓글