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

같은 테이블을 두번 조인 하려고 할때 어떻게 해야 할까요? #515

Closed WonHyeongCho closed 10 months ago

WonHyeongCho commented 10 months ago

예를들어 다음과 같은 쿼리가 있을 때

SELECT A.*, B.user_name, C.user_name FROM Company as A JOIN Member as B ON A.founder_id = B.id JOIN Member as C ON A.manager_id = C.id

어떻게 JDSL 로 접근할 수 있을까요?

shouwn commented 10 months ago

안녕하세요.

여러가지 방법이 있을 수 있습니다. 아래 문서를 참고해주세요. https://kotlin-jdsl.gitbook.io/docs/jpql-with-kotlin-jdsl/entities#alias

예제로 아래와 같은 2개의 형태를 만들어볼 수 있습니다.

authorRepository.findAll {
    val authorA = entity(Author::class, "authorA")
    val authorB = entity(Author::class, "authorB")

    select<Tuple>(
        entity(BookAuthor::class),
        authorA(Author::name),
        authorB(Author::name),
    ).from(
        entity(BookAuthor::class),
        join(authorA).on(path(BookAuthor::authorId).equal(authorA.path(Author::authorId))),
        join(authorB).on(path(BookAuthor::authorId).equal(authorB.path(Author::authorId))),
    )
}

authorRepository.findAll {
    val authorA = entity(Author::class, "authorA")
    val authorB = entity(Author::class, "authorB")

    select<Tuple>(
        entity(BookAuthor::class),
        authorA(Author::name),
        authorB(Author::name),
    ).from(
        entity(BookAuthor::class),
        join(entity(Author::class))
            .on(path(BookAuthor::authorId).equal(authorA.path(Author::authorId)))
            .alias(authorA),
        join(entity(Author::class))
            .on(path(BookAuthor::authorId).equal(authorB.path(Author::authorId)))
            .alias(authorB),
    )
}

두 쿼리 모두 아래의 JPQL로 랜더링 됩니다.

SELECT 
    BookAuthor, 
    authorA.name, 
    authorB.name 
FROM 
    BookAuthor AS BookAuthor 
    INNER JOIN Author AS authorA ON BookAuthor.authorId = authorA.authorId 
    INNER JOIN Author AS authorB ON BookAuthor.authorId = authorB.authorId
WonHyeongCho commented 10 months ago

답변해주셔서 감사합니다!

혹시 JDSL 에서 쿼리를 재사용할 수 있을까요?

mybatis 의 , 기능이요.

shouwn commented 10 months ago

아 태그를 적어주셔서 안 보였군요. <sql>, <include>와 비슷한 기능도 가능합니다.

어떻게 코드를 재활용할지는 각자의 스타일이 달라서 제가 말씀드리기 어렵지만 제가 추천드리는 방법은 우리 팀에서 쓸 DSL을 정의하는 것입니다.

자세한 내용은 아래를 참고해주세요. https://kotlin-jdsl.gitbook.io/docs/jpql-with-kotlin-jdsl/custom-dsl#dsl

예를 들면 아래처럼 나만의 Jpql 클래스를 정의한 뒤에

class BookJpql : Jpql() {
    companion object Constructor : JpqlDsl.Constructor<BookJpql> {
        override fun newInstance(): BookJpql = BookJpql()
    }

    fun Path<BookPrice>.greaterThan(price: Int): Predicate {
        return this@greaterThan.path(BookPrice::value).greaterThan(BigDecimal.valueOf(price.toLong()))
    }

    fun Entity<Book>.salePriceBetween(min: Int, max: Int): Predicate {
        return this@salePriceBetween.path(Book::salePrice)(BookPrice::value).between(
            BigDecimal.valueOf(min.toLong()),
            BigDecimal.valueOf(max.toLong()),
        )
    }
}

이를 아래처럼 사용할 수 있습니다.

bookRepository.findAll(BookJpql) {
    select(
        entity(Book::class),
    ).from(
        entity(Book::class),
    ).whereAnd(
        entity(Book::class)(Book::price).greaterThan(100),
        entity(Book::class).salePriceBetween(100, 200),
    )
}

이런 식으로 만들면 코드 중복 및 조금 더 도메인 특화된 DSL을 만들 수 있고, 메소드이기 때문에 추적이 용이하게 됩니다.

WonHyeongCho commented 10 months ago

답변 감사드립니다!