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
711 stars 88 forks source link

join 관련 사용법 문의 #77

Closed LeeMH closed 2 years ago

LeeMH commented 2 years ago

안녕하세요.

QueryDSL 대신 jdsl 로 간단하게 토이프로젝트 돌려보며 방향성을 잡아 나가고 있습니다.

동일 테이블에 대한 join 을 어떻게 해야 할지 이리저리 해보다 안되서 문의 드립니다.

User 테이블에는 상위 User의 정보를 저장하는 parent_id가 있습니다. User Entity에서는 연관관계 없이 사용하고 있습니다. User( id bigint, parent_id bigint, name varchar(100) )

표현하고 싶은 쿼리는 아래와 같습니다. 문서에 join > alias쪽을 보았으나, native sql 처럼 풀어나가는 방법이 없는것 같아 문의 드립니다. select u.id as my_id, p.id as parent_id from User u left join User p on u.parent_id = u.id

shouwn commented 2 years ago

안녕하세요. 아래와 같이 사용하실 수 있습니다.

@Entity
@Table(
    name = "test_user"
)
data class User(
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,

    @Column(name = "parent_id")
    val parentId: Long?,

    @Column(name = "name")
    val name: String,
)

data class Row(
    val parentId: Long?,
    val childId: Long?,
)

val rows = queryFactory.listQuery<Row> {
    val parentUser = entity(User::class, "parentUser")
    val childUser = entity(User::class, "childUser")

    selectMulti(col(parentUser, User::id), col(childUser, User::id))
    from(parentUser)
    join(childUser, col(parentUser, User::id).equal(col(childUser, User::parentId)))
}

rows.forEach {
    println(it)
}

하지만 이렇게 condition 기반의 조인을 할 경우 left join을 할 수 없습니다. 이는 Criteria API의 한계이고, 추후 kotlin-jdsl을 JPQL을 기반으로 변경하면 지원할 수 있고 변경 계획은 있지만 다른 업무로 인해 아직 많은 진척은 없는 상황입니다.

만약 변경 된다면 3.0으로 배포하여 많은 인터페이스가 바뀌게 될 예정입니다.

만약 left join이 꼭 필요하시다면 비지니스 모델에 따라 가능할지 모르지만 아래와 같은 Entity 모델을 추천 드립니다.

@Entity
@Table(
    name = "test_user"
)
data class User(
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,

    @Column(name = "name")
    val name: String,

    @OneToMany(mappedBy = "parent", cascade = [CascadeType.ALL])
    val children: Set<User>,
) {
    @ManyToOne
    @JoinColumn(name = "parent_id")
    var parent: User? = null

    init {
        children.forEach { it.parent = this }
    }
}

data class Row(
    val parentName: String?,
    val childName: String?,
)

val rows = queryFactory.listQuery<Row> {
    val parentUser = entity(User::class, "parentUser")
    val childUser = entity(User::class, "childUser")

    selectMulti(col(parentUser, User::name), col(childUser, User::name))
    from(parentUser)
    join(parentUser, childUser, on(User::children), JoinType.LEFT)
    orderBy(col(parentUser, User::name).asc())
}

rows.forEach {
    println(it)
}

// Row(parentName=user1, childName=user2)
// Row(parentName=user2, childName=user3)
// Row(parentName=user3, childName=null)
LeeMH commented 2 years ago

@shouwn 친절한 답변 감사합니다. jdsl 고맙게 잘 사용하고 있습니다. left join만 가능하면 최고일듯 합니다!!!

SQL 베이스의 통계성 업무가 꼭 있어서, JPA(QueryDSL)와 myBatis 사이에서 고민이 jdsl로 사라졌으면 합니다.

감사합니다.