RunUs 프로젝트에 jOOQ 도입 이유와 사용 후기

August 11, 2024


jOOQ가 무엇인지는 원작자의 자세한 설명이 더욱 도움될 것으로 생각합니다.

1. jOOQ를 도입한 계기

8월인 지금, 연합 IT 동아리인 DND에서 프로젝트 RunUs를 개발하고 있습니다.

JPQL로는 나타내기 복잡한 쿼리 작성을 위해 QueryDSL이나 jOOQ와 같은 쿼리 빌더 라이브러리가 필요했습니다.

다른 팀원분이 JPA를 다뤄본 경험이 적기 때문에, SQL처럼 사용할 수 있는 jOOQ를 도입해 사용해보았습니다.

2. 문법 비교

  • Native SQL

    SELECT COUNT(*) AS total_count, SUM(amount) AS total_amount
    FROM orders
    WHERE status = 'COMPLETED'
    GROUP BY category;
  • jOOQ

    return dsl.select(count().as("total_count"), sum(ORDERS.AMOUNT).as("total_amount"))
        .from(ORDERS)
        .where(ORDERS.STATUS.eq(OrderStatus.COMPLETED))
        .groupBy(ORDERS.CATEGORY)
        .fetch();

3. jOOQ 장점

ORM이나 SQL이냐의 차이보다는 문법이나 개인적 경험에 따라 작성했으니 참고해주세요!

  1. jOOQ에서는 Mapper(select 한 필드들을 java pojo 객체로)를 사용한다면, jOOQ를 사용하는 계층에서 변환 클래스를 작성 가능

    • QueryDSL에서는 @QueryProjection을 사용해서 domain이나 dto 계층에 QueryDSL 의존성이 필요
  2. 기존에 JPA를 사용하지 않더라도 type-safe한 SQL 작성 가능

4. jOOQ 단점

  1. 오픈 소스 RDB(MySQL, PostgreSQL 등) 최신 버전은 무료지만, 구버전 DB에서는 jOOQ를 무료로 사용할 수 없습니다.

  2. Hibernate에서는 auditing 기능이 있지만, jOOQ에서는 유로 라이센스를 사용해야 합니다.

  3. RDB에는 VARCHAR을 사용하지만 java 코드에서는 Enum으로 불러오고 싶다면, gradle 파일이나 java 설정 파일에 추가적으로 enum class의 패키지 위치와 converter 클래스의 패키지 위치도 문자열로 작성해야 합니다.

    • DB 스키마를 바탕으로 DSL을 작성하므로, jakarta.persistence.Enumerated, jakarta.persistence.Convert처럼 어노테이션 방식을 사용 불가
  4. 초기 설정이 복잡

    • JPA Entity 어노테이션을 스캔해서 DSL을 생성하는 QueryDSL과 달리 jOOQ는 code generation 방식을 프로젝트에 맞게 골라 설정해야 합니다.
      1. JPA Entity 기반

      2. testcontainers + flyway

      3. DB 연결 후 스캔

5. 생각해볼 점

Service 계층에서는 JPA나 jOOQ를 사용하는지 내부 구현은 몰라야 합니다.

  • 같은 transaction 내에 JPA 메소드와 jOOQ 쿼리를 같이 사용할 수 있어야 합니다.

하지만 JPA는 쓰기 지연을 하기 때문에, 실제로 DB에 바로 쿼리를 보내지 않습니다.

그래서 jOOQ를 사용한 update 쿼리를 사용한다면, JPA 1차 캐시에 반영되지 않아서 불일치하는 문제가 생길 수 있습니다.

  • QueryDSL에서는 entityManager.flush(), clear()로 처리할 수 있지만, jOOQ에서 entityManager의 함수를 호출하는 건 의존 방향이 적절하지 않습니다.

    • 아래처럼 jpa와 jooq repository를 감싸는 계층에서 관리를 하는 방법도 있을 것 같습니다.
    public void updateMemberLevel(long memberId, int plusExp) {
        entityManager.flush();
        entityManager.clear();
        jooqMemberLevelRepository.updateMemberLevel(memberId, plusExp);
    }

6. 결론

여러 장단점이 있지만 JPQL이나 jdbc을 사용해 문자열로 작성하는 대신 type-safe한 쿼리를 작성할 수 있다는 장점은 있습니다.

프로젝트에 특성에 따라 고민 후 도입해보면 좋을 것 같습니다.


Profile picture

이재원

이해하기 쉬운 코드를 작성하려 고민합니다.


© 2024 Won's blog Built with Gatsby