dependencies { // JOOQ + Spring Data JDBC 함께 사용 implementation 'org.springframework.boot:spring-boot-starter-jooq' implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'// Spring Data JDBC 같이 테스트 implementation 'com.h2database:h2' }
dependencies { // JOOQ code generation (DDLDatabase 사용) // jooq-codegen은 Spring Boot가 관리하므로 버전 생략 가능 // jooq-meta-extensions는 Spring Boot가 관리하지 않으므로 버전 명시 필요 jooqCodegen 'org.jooq:jooq-codegen'// Spring Boot가 관리하는 버전 사용 jooqCodegen 'org.jooq:jooq-meta-extensions:3.18.13'// DDLDatabase 지원 (버전 명시 필요) jooqCodegen 'com.h2database:h2' }
장점:
타입 안전성: 컴파일 타임에 쿼리 오류를 발견할 수 있다.
SQL 친화적: SQL에 가까운 형태로 쿼리를 작성할 수 있다.
복잡한 쿼리: 복잡한 SQL 쿼리를 쉽게 작성할 수 있다.
동적 쿼리: 조건에 따라 동적으로 쿼리를 생성할 수 있다.
성능: 생성된 쿼리가 최적화되어 있다.
단점:
코드 생성: 스키마 변경 시 코드 재생성이 필요하다.
학습 곡선: jOOQ 문법을 익혀야 한다.
의존성: 데이터베이스 스키마에 의존적이다.
코드생성
데이터베이스 연결 혹은 지정한 DDL 파일로부터 코드를 생성한다.
아래는 src/main/resources/jooq-codegen.xml 을 통해 코드생성하는 과정.
-- 사용자(Author) 테이블 생성 CREATE TABLE IF NOTEXISTS author ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(255) NOT NULLUNIQUE, created_at TIMESTAMPDEFAULTCURRENT_TIMESTAMP, updated_at TIMESTAMPDEFAULTCURRENT_TIMESTAMP );
-- 게시판 테이블 생성 (author_id 추가) CREATE TABLE IF NOTEXISTS board ( id BIGINT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, content TEXT NOT NULL, author_id BIGINTNOT NULL, created_at TIMESTAMPDEFAULTCURRENT_TIMESTAMP, updated_at TIMESTAMPDEFAULTCURRENT_TIMESTAMP, view_count INTDEFAULT0, FOREIGN KEY (author_id) REFERENCES author(id) ONDELETE CASCADE );
-- 댓글 테이블 생성 CREATE TABLE IF NOTEXISTS comment ( id BIGINT AUTO_INCREMENT PRIMARY KEY, board_id BIGINTNOT NULL, content TEXT NOT NULL, author VARCHAR(100) NOT NULL, created_at TIMESTAMPDEFAULTCURRENT_TIMESTAMP, updated_at TIMESTAMPDEFAULTCURRENT_TIMESTAMP, FOREIGN KEY (board_id) REFERENCES board(id) ONDELETE CASCADE );
CREATE INDEX IF NOTEXISTS idx_board_id ON comment(board_id); CREATE INDEX IF NOTEXISTS idx_board_created_at ON board(created_at); CREATE INDEX IF NOTEXISTS idx_board_author_id ON board(author_id); CREATE INDEX IF NOTEXISTS idx_author_email ON author(email);
// INSERT @Transactional public BoardDto createBoard(BoardCreateRequest request) { Instantnow= Instant.now();
// INSERT 실행 후 생성된 ID 반환 (returning() 사용) // 주의: MySQL/H2는 RETURNING 절을 지원하지 않을 수 있음 Record1<Long> boardRecord = dsl.insertInto(BOARD) .set(BOARD.TITLE, request.getTitle()) .set(BOARD.CONTENT, request.getContent()) .set(BOARD.AUTHOR_ID, request.getAuthorId()) .set(BOARD.CREATED_AT, now) .set(BOARD.UPDATED_AT, now) .set(BOARD.VIEW_COUNT, 0) .returningResult(BOARD.ID) // 자동으로 MySQL/H2 fallback: LAST_INSERT_ID() 사용 .fetchOne();
@Repository publicinterfaceBoardRepositoryextendsCrudRepository<Board, Long> { // 생성자 기반 자동 매핑 시도 (컬럼 순서와 생성자 매개변수 순서 일치 필요) @Query(""" SELECT b.id, b.title, b.content, b.author_id, b.created_at, b.updated_at, b.view_count, a.id AS author_id_value, a.name AS author_name, a.email AS author_email, a.created_at AS author_created_at, a.updated_at AS author_updated_at FROM board b INNER JOIN author a ON b.author_id = a.id ORDER BY b.id DESC """) List<BoardWithAuthorDto> findAllWithAuthor(); }
@Service @RequiredArgsConstructor publicclassBoardService { privatefinal BoardRepository boardRepository; // Spring Data JDBC privatefinal DSLContext dsl; // JOOQ (동적 쿼리용) // 단순 조회는 Spring Data JDBC 사용 public List<BoardWithAuthorDto> findAllWithAuthor() { return boardRepository.findAllWithAuthor(); } // 동적 쿼리는 jOOQ 사용 public List<BoardAuthorDto> searchBoards(BoardSearchRequest request) { // ... jOOQ 동적 쿼리 } }