본문 바로가기
Blog/TIL

2025-03-14 (금)

by 코젼 2025. 3. 14.
728x90
반응형

🔹 1️⃣ 쿼리 최적화: JOIN을 활용한 단일 쿼리 조회

✔ 문제점

기존에는 여러 개의 테이블을 개별적으로 조회한 후, reportId를 기준으로 매핑하는 방식이었다.

이 방식은 쿼리 실행 횟수가 많아지고, 추가적인 연산이 필요하여 성능 저하를 유발할 수 있었다.

✔ 해결 방법

  • 여러 개의 개별 쿼리를 실행하는 대신, JOIN을 활용하여 한 번의 쿼리로 데이터를 조회하도록 변경했다.
  • Report 엔티티와 관련된 데이터를 LEFT JOIN을 통해 조회하고, 최신 데이터(createdAt 기준)를 가져오는 서브쿼리를 적용했다.
  • 불필요한 데이터 연산을 줄이고, 쿼리 실행 시점에서 필요한 데이터를 가공하여 반환하는 방식으로 개선했다.

✔ 개선된 방식

val latestHistorySubquery = select(
    max(path(History::createdAt))
).from(
    entity(History::class)
).where(
    path(History::reportId).eq(path(Report::tsid))
).asSubquery()

val responses = repository.findPage(pageable) {
    selectNew<ResponseDTO>(
        path(Report::tsid),
        path(Report::name),
        path(History::fileId),
        path(History::createdAt)
    ).from(
        entity(Report::class),
        leftJoin(History::class).on(
            path(History::reportId).eq(path(Report::tsid))
                .and(path(History::createdAt).eq(latestHistorySubquery)) // 🔥 최신 데이터만 선택
        )
    ).whereAnd(
        path(Report::projectId).eq(projectId)
    ).orderBy(
        path(Report::createdAt).desc()
    )
}

✔ 결과

  • ✅ 여러 개의 쿼리 실행 → 단일 쿼리로 변환 (JOIN)
  • ✅ 불필요한 groupBy() & associateBy() 제거 → 쿼리 실행 시점에서 데이터 정리
  • ✅ 최신 데이터 조회 시 서브쿼리 활용하여 max(createdAt) 필터링
  • ✅ 쿼리 실행 속도 및 전체 성능 최적화

🔹 2️⃣ 서브쿼리 분리로 가독성 향상

쿼리에서 서브쿼리를 직접 leftJoin 내부에 작성하면 가독성이 떨어지고, 재사용이 어렵다.

따라서 서브쿼리를 별도로 변수로 분리하여 가독성과 유지보수성을 높이는 방식을 적용했다.

val latestHistorySubquery = select(
    max(path(History::createdAt))
).from(entity(History::class))
.where(path(History::reportId).eq(path(Report::tsid)))
.asSubquery()

val responses = repository.findPage(pageable) {
    selectNew<ResponseDTO>(
        path(Report::tsid),
        path(Report::name),
        path(History::fileId),
        path(History::createdAt)
    ).from(
        entity(Report::class),
        leftJoin(History::class).on(
            path(History::reportId).eq(path(Report::tsid))
                .and(path(History::createdAt).eq(latestHistorySubquery)) // 🔥 서브쿼리 적용
        )
    )
}

✔ 결과

  • ✅ 쿼리 가독성 향상 (서브쿼리를 별도로 정의)
  • ✅ 서브쿼리 재사용 가능 (필요 시 다른 로직에서도 활용 가능)

🔹 3️⃣ stringLiteral() 활용법

✔ 문제점

쿼리 내에서 문자열 값을 직접 비교해야 하는 경우, eq() 등을 사용하면 런타임 오류가 발생할 가능성이 있었다.

Kotlin JDSL에서는 stringLiteral()을 사용하면 JPQL에서 안전하게 문자열 값을 다룰 수 있다.

✔ 해결 방법

import com.linecorp.kotlinjdsl.querymodel.jpql.expression.Expressions.stringLiteral

val responses = repository.find {
    selectFrom(entity(Report::class))
        .where(path(Report::name).eq(stringLiteral("Sample Report"))) // 🔥 문자열 값을 안전하게 비교
}

✔ stringLiteral()의 장점 ✅ JPQL에서 안전하게 문자열 값을 표현 가능 ✅ eq(), like(), in() 등의 조건에서 문자열을 안전하게 비교 ✅ 직접 문자열을 사용했을 때 발생할 수 있는 런타임 오류 방지

🔹 4️⃣ 예외 처리 개선

✔ 문제점

  • 특정 조건(예: WEEKLY에서 요일이 여러 개 입력되는 경우)에 대해 예외 처리가 부족했다.
  • 잘못된 값이 들어왔을 때 명확한 오류 메시지를 제공할 필요가 있었다.

✔ 해결 방법

when (repeatCycle) {
    WEEKLY -> {
        if (repeatDays.size > 1) {
            throw ServiceException(ErrorCode.INVALID_REPEAT_DAYS) // ✅ 요일이 여러 개면 예외 발생
        }
        if (repeatDays.any { it !in validWeekDays }) {
            throw ServiceException(ErrorCode.INVALID_REPEAT_DAYS) // ✅ 유효하지 않은 요일이면 예외 발생
        }
    }
    MONTHLY -> {
        if (repeatDays.any { it.toIntOrNull() !in 1..31 }) {
            throw ServiceException(ErrorCode.INVALID_REPEAT_DAYS) // ✅ 1~31 범위 밖이면 예외 발생
        }
    }
}

✔ 결과

  • ✅ 불필요한 데이터 입력 방지
  • ✅ 잘못된 입력 시 명확한 오류 메시지 제공
  • ✅ 비즈니스 로직 안정성 향상
728x90
반응형

'Blog > TIL' 카테고리의 다른 글

2025-03-20 (목)  (0) 2025.03.20
2025-03-18 (화)  (2) 2025.03.18
2025-03-13 (목)  (0) 2025.03.13
2025-03-11 (화)  (1) 2025.03.11
2025-03-10 (월)  (1) 2025.03.10

댓글