백엔드 면접 용어 사전
Spring·JPAJava Object Oriented Querying

jOOQ

DB 스키마에서 자바 코드를 생성해 SQL을 타입 안전하게 작성하는 라이브러리. 컬럼명·타입 오류를 컴파일 시점에 잡는다.

SQL을 문자열이 아니라 자바 코드로 쓰게 해 주는 라이브러리. 이름은 Java Object Oriented Querying의 약자다.

어떻게 타입 안전해지나 — 코드 생성

빌드 단계에서 실제 DB 스키마를 읽어 테이블·컬럼에 대응하는 자바 클래스를 만들어 둔다.

[빌드 시점]
DB 스키마 (orders 테이블: id BIGINT, user_id BIGINT, amount DECIMAL, created_at TIMESTAMP)
     ↓  jOOQ 코드 생성기
생성된 자바 클래스: ORDERS.ID(Long), ORDERS.USER_ID(Long),
                   ORDERS.AMOUNT(BigDecimal), ORDERS.CREATED_AT(LocalDateTime)

그래서 쿼리를 쓸 때 컬럼이 자바 필드가 된다.

List<OrderSummary> rows = dsl
    .select(ORDERS.USER_ID, count(), sum(ORDERS.AMOUNT))
    .from(ORDERS)
    .where(ORDERS.CREATED_AT.ge(from))   // 컬럼이 없거나 타입이 안 맞으면 컴파일 에러
    .groupBy(ORDERS.USER_ID)
    .having(count().gt(3))
    .fetchInto(OrderSummary.class);

ORDERS.CREATD_AT처럼 오타를 내면 빌드가 실패한다. 문자열 SQL이나 JPA 네이티브 쿼리였다면 운영에서 터졌을 오류다. ge(greater or equal)에 문자열을 넘기는 것도 컴파일 단계에서 막힌다.

무엇에 강한가

jOOQJPA/Hibernate
사고의 출발점SQL — 원하는 쿼리를 그대로 표현객체 — 엔티티 관계를 다루면 SQL은 프레임워크가
윈도우 함수·CTE·DB 고유 문법그대로 표현 가능네이티브 쿼리로 우회
오류 발견 시점컴파일실행 (네이티브 쿼리는 런타임)
캐싱·변경 감지없음영속성 컨텍스트가 제공

QueryDSL과의 차이

둘 다 컴파일 타임 타입 안전성을 주지만 코드를 생성하는 원천이 다르다.

QueryDSL : JPA 엔티티Q클래스 생성(엔티티가 있어야 한다)
jOOQ: DB 스키마테이블 클래스 생성 (엔티티 없이 SQL 자체를 다룬다)

실무에서 자리잡은 조합

프로젝트 단위로 하나만 고르는 문제가 아니라 경로별로 나누는 것이 지금의 실용적인 답이다.

  • 쓰기 경로 → JPA/Hibernate — 영속성 컨텍스트·변경 감지·엔티티 매핑이 실제로 값을 한다.
  • 읽기 경로 → jOOQ 또는 JdbcTemplate — 통계·리포트·다중 조인처럼 쿼리 모양과 성능을 직접 통제해야 하는 곳.

대부분의 서비스가 읽기 9 : 쓰기 1이라 자연스러운 분리이고, CQRS(명령과 조회의 책임을 분리하는 패턴)라는 이름을 붙이지 않아도 성립한다. 밑바닥은 같은 JDBC라 커넥션 풀·트랜잭션 경계 이야기는 그대로 적용된다.

주의

빌드에 코드 생성 단계가 추가되므로 스키마 변경 시 재생성이 필요하고, 상용 DB(Oracle·SQL Server 등) 지원은 유료 라이선스다.

코드 생성이 빌드에 들어간다

// build.gradle.kts — 실제 DB(또는 마이그레이션이 적용된 컨테이너)를 읽어 클래스를 만든다
jooq {
    configurations {
        create("main") {
            jooqConfiguration.apply {
                jdbc.apply { url = "jdbc:postgresql://localhost:5432/app" }
                generator.apply {
                    database.apply { inputSchema = "public" }
                    target.apply { packageName = "com.example.jooq" }
                }
            }
        }
    }
}

스키마 변경 → 마이그레이션 적용 → 코드 재생성 → 컴파일

  • 이 사슬이 끊기면 빌드가 깨진다 (그게 장점이다)

CI에서 Testcontainers로 빈 DB를 띄우고 마이그레이션을 적용한 뒤 생성하는 방식이 흔하다. 개발자 로컬 DB 상태에 빌드가 의존하지 않게 하려는 것이다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 모던 자바·스프링 — 가상 스레드·JEP 491·리액티브·jOOQ·Boot 4