Java 아키텍처·Spring 용어 사전
JDBCjOOQ · DataAccessException · RowMapper

JdbcTemplate

JDBC 보일러플레이트를 걷어낸 얇은 래퍼. SQL은 직접 쓰되 반복 코드와 예외 변환을 프레임워크에 맡긴다.

JDBC의 반복 코드를 프레임워크에 위임하고 SQL과 매핑만 우리가 쓰는 얇은 래퍼.

무엇을 대신해 주나

// 커넥션 열기·닫기 · Statement 생성 · 예외 처리 · ResultSet 순회를 전부 대신한다
List<Member> list = jdbcTemplate.query(
    "SELECT id, name FROM member WHERE status = ?",
    (rs, rowNum) -> new Member(rs.getLong("id"), rs.getString("name")),
    "ACTIVE");

// 이름 있는 파라미터를 쓰려면
namedJdbcTemplate.query("SELECT ... WHERE status = :status",
    Map.of("status", "ACTIVE"), rowMapper);

예외 계층 변환

JDBC 의 SQLException 은 검사 예외이고, 오류 코드가 DB 마다 다르다

  • Spring 이 DataAccessException(언체크) 계층으로 변환한다

DuplicateKeyException · DeadlockLoserDataAccessException · EmptyResultDataAccessException ...

  • DB 를 바꿔도 애플리케이션 예외 처리가 그대로 동작한다

검사 예외가 언체크로 바뀐다는 점@Transactional의 롤백 규칙과도 맞물린다 — 기본 규칙으로도 롤백된다.

어디에 쓰나

JPA 가 어색한 곳에 쓴다

  • 복잡한 통계·리포트 쿼리
  • 대량 배치 (영속성 컨텍스트가 오히려 부담)
  • 성능이 민감해 SQL 을 직접 통제해야 하는 구간

jOOQ — 타입 안전한 대안

// DB 스키마에서 자바 클래스를 생성해 두고, SQL 을 코드로 쓴다
dsl.select(ORDERS.USER_ID, count())
   .from(ORDERS)
   .where(ORDERS.CREATED_AT.ge(from))    // 컬럼명 오타·타입 불일치가 컴파일 에러
   .groupBy(ORDERS.USER_ID)
   .fetch();
  • JdbcTemplate — SQL 이 문자열 → 오타가 런타임에 드러난다
  • jOOQ — SQL 이 코드 → 컴파일 시점에 걸린다. 대신 코드 생성 단계가 붙는다

요즘의 실무 조합

쓰기 경로JPA/Hibernate영속성 컨텍스트·변경 감지가 값을 한다
읽기 경로jOOQ · JdbcTemplate쿼리 모양과 성능을 직접 통제한다

대부분의 서비스가 읽기 9 : 쓰기 1 이라 자연스러운 분리다 밑바닥은 같은 JDBC 라 커넥션 풀·트랜잭션 경계 이야기는 그대로 적용된다

면접 함정

  • "JdbcTemplate은 구식" → JPA가 어색한 구간의 표준 도구로 지금도 널리 쓴다.
  • "JPA와 JdbcTemplate을 섞으면 안 된다" → 같은 트랜잭션·같은 커넥션을 공유한다. 다만 JPA의 쓰기 지연 때문에 flush 시점을 의식해야 한다.

배치 처리

jdbcTemplate.batchUpdate(
    "INSERT INTO member (name, email) VALUES (?, ?)",
    members, 1000,                              // 1000건씩 묶는다
    (ps, m) -> { ps.setString(1, m.name()); ps.setString(2, m.email()); });

JPA의 saveAll()보다 훨씬 빠르다 — 영속성 컨텍스트에 엔티티를 쌓지 않고 스냅샷도 만들지 않기 때문이다. 대량 적재는 JdbcTemplate이 맞는 자리다.

JPA와 섞을 때의 함정

@Transactional
void mix(Long id) {
    Member m = repo.findById(id).orElseThrow();
    m.setName("변경");                                    // 아직 UPDATE 안 나감 (쓰기 지연)

    jdbcTemplate.queryForObject("SELECT name FROM member WHERE id=?", String.class, id);
    // ← 옛 값을 읽는다! JdbcTemplate 은 영속성 컨텍스트를 모른다

    em.flush();                                          // 먼저 flush 해야 한다
}

같은 커넥션·같은 트랜잭션이지만 JPA의 쓰기 지연 버퍼는 공유되지 않는다. 섞어 쓸 때는 flush 시점을 반드시 의식한다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — JDBC — 자바가 DB와 말하는 가장 낮은 층