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)에 문자열을 넘기는 것도 컴파일 단계에서 막힌다.
무엇에 강한가
| jOOQ | JPA/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 상태에 빌드가 의존하지 않게 하려는 것이다.