line / kotlin-jdsl

Kotlin library that makes it easy to build and execute queries without generated metamodel
https://kotlin-jdsl.gitbook.io/docs/
Apache License 2.0
686 stars 85 forks source link

join 사용법 문의 #766

Closed hyunjungkimm closed 1 week ago

hyunjungkimm commented 1 week ago

안녕하세요! jdsl을 처음 사용해보는데 에러가 발생해서 질문드립니다.

해당 쿼리를 jdsl로 작성하는 중에 join에서 에러가 발생합니다. // "errorMessage": "org.hibernate.query.SemanticException: Cannot join to root entity 'FoodSpotsReview'" 서브쿼리에서 조인을 잘 못 사용하고 있는건가요??

참고 에서처럼 join(ReviewLike::review),이렇게도 조인이 가능한가요?? -- 에러는 발생하지 않지만 정렬이 제대로 동작하지 않습니다!

SELECT
    u.NICKNAME,
    SUM(f.LIKE_TOTAL) AS TOTAL_LIKES
FROM
    FOOD_SPOTS_REVIEWS f
        JOIN
    USERS u ON f.USER_ID = u.ID
GROUP BY
    u.ID, u.NICKNAME
ORDER BY
    TOTAL_LIKES DESC,
    (SELECT MAX(l.CREATED_DATETIME)
     FROM REVIEW_LIKES l
              JOIN FOOD_SPOTS_REVIEWS f2 ON l.REVIEW_ID = f2.ID
     WHERE f2.USER_ID = u.ID) ASC;
 override fun findAllUserLikeCount(): List<UserRanking> =
        kotlinJdslJpqlExecutor
            .findList {
                val userIdPath = path(FoodSpotsReview::user).path(User::id)
                val userNicknamePath = path(FoodSpotsReview::user).path(User::profile).path(Profile::nickname)
                val likeTotalPath = path(FoodSpotsReview::likeTotal)
                val createdAtPath = path(ReviewLike::createdDateTime)

                val subQuery =
                    select<LocalDateTime>(
                        max(createdAtPath),
                    ).from(
                        entity(ReviewLike::class),
                        // "errorMessage": "org.hibernate.query.SemanticException: Cannot join to root entity 'FoodSpotsReview'"
                        // join(FoodSpotsReview::class).on( path(ReviewLike::review).path(FoodSpotsReview::id).eq(path(FoodSpotsReview::id)))
                    ).where(
                        path(ReviewLike::review).path(FoodSpotsReview::user).path(User::id).eq(userIdPath),
                    ).asSubquery()

                selectNew<UserRanking>(
                    userNicknamePath,
                    sum(likeTotalPath),
                ).from(
                    entity(FoodSpotsReview::class),
                ).groupBy(userIdPath, userNicknamePath)
                    .orderBy(
                        sum(likeTotalPath).desc(),
                        subQuery.asc(),
                    )
            }

확인해주시면 감사하겠습니다!

shouwn commented 1 week ago

hyunjungkimm 님 안녕하세요.

에러의 원인은 select query에 있는 FoodSpotsReview entity와 subquery에 있는 FoodSpotsReview entity 가 충돌했기 때문입니다. 두 entity의 이름이 같아 충돌한 것으로 native query에서 작성하신 것처럼 select query와 subquery 각각에서 alias를 주셔야 합니다.

예를 들어 Book 이라는 entity로 작성해주신 쿼리와 비슷한 쿼리를 작성해보면 다음과 같습니다.

val bookAuthor1 = entity(BookAuthor::class, "BookAuthor1")
val bookAuthor2 = entity(BookAuthor::class, "BookAuthor2")

val subquery = select(
    max(Author::name)
).from(
    entity(Author::class),
    join(bookAuthor2)
        .on(bookAuthor2(BookAuthor::authorId).eq(path(Author::authorId)))
).where(
    bookAuthor1(BookAuthor::authorId).eq(bookAuthor2(BookAuthor::authorId))
).asSubquery()

select(
    bookAuthor1(BookAuthor::authorId)
).from(
    bookAuthor1
).orderBy(
    subquery.asc()
)

entity에 alias를 줄 때에는 entity 함수에 alias를 추가 파라미터로 넣어주시면 됩니다. entity의 .invoke 혹은 .path 함수를 통해 entity의 필드를 표현할 수 있습니다.

말씀해주신 join(ReviewLike::review)도 사용 가능합니다. 이는 JPQL의 연관관계를 이용한 조인으로 JPQL의 ReviewLike.review와 동일한 문법이 됩니다. 이를 사용했을 때에도 정상동작하지 않는 것은 alias가 없기 때문에 잘못된 subquery가 생성되어 동작했을 수 있습니다.

연관관계 조인을 사용하실 때 alias를 넣는 방법은 join(ReviewLike::review).alias(entity1)과 같이 alias 메소드를 호출해주시면 됩니다.

참고 문서: https://kotlin-jdsl.gitbook.io/docs/jpql-with-kotlin-jdsl/entities#alias https://kotlin-jdsl.gitbook.io/docs/jpql-with-kotlin-jdsl/paths https://kotlin-jdsl.gitbook.io/docs/jpql-with-kotlin-jdsl/statements#join

hyunjungkimm commented 1 week ago

감사합니다!!