/** * 컬렉션은 별도로 조회 * Query: 루트 1번, 컬렉션 N 번 * 단건 조회에서 많이 사용하는 방식 */ funfindOrderQueryDtos(): List<OrderQueryDto> { //루트 조회(toOne 코드를 모두 한번에 조회) val result = findOrders()
//루프를 돌면서 컬렉션 추가(추가 쿼리 실행) result.forEach { val orderItems = findOrderItems(it.orderId) it.orderItems = orderItems } return result }
/** * 1:N 관계(컬렉션)를 제외한 나머지를 한번에 조회 */ privatefunfindOrders(): List<OrderQueryDto> { return em.createQuery( "select new kotlinbook.jpashop.repository.order.query.OrderQueryDto(o.id, m.name, o.orderDate, o.status, d.address)" + " from Order o" + " join o.member m" + " join o.delivery d", OrderQueryDto::class.java ).resultList }
/** * 1:N 관계인 orderItems 조회 */ privatefunfindOrderItems(orderId: Long): List<OrderItemQueryDto> { return em.createQuery( "select new kotlinbook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)" + " from OrderItem oi" + " join oi.item i" + " where oi.order.id = : orderId", OrderItemQueryDto::class.java ).setParameter("orderId", orderId).resultList }
}
OrderQueryDto.kt
1 2 3 4 5 6 7 8 9
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy::class) dataclassOrderQueryDto( val orderId: Long, val name: String, val orderDate: LocalDateTime, //주문시간 val orderStatus: OrderStatus, val address: Address, var orderItems: List<OrderItemQueryDto>? = null, )
먼저 ‘ToOne(N:1, 1:1)’ 관계를 조회하고, 이후에 ‘ToMany(1:N)’ 관계를 각각 개별적으로 처리하는 전략을 선택했습니다. 이 방식을 선택한 이유는 다음과 같습니다: ‘ToOne’ 관계의 경우, 이들을 결합해도 데이터의 행(row) 수가 증가하지 않습니다. 이는 ‘ToOne’ 관계를 결합하여 최적화하는 것이 상대적으로 용이하다는 것을 의미합니다. 따라서 이런 관계들은 한 번에 조회합니다. 반면에 ‘ToMany(1:N)’ 관계를 결합하면 데이터의 행 수가 증가하게 됩니다. 이런 경우 최적화하는 것이 어렵기 때문에, findOrderItems()와 같은 별도의 메서드를 이용하여 각각을 조회합니다.
먼저 ‘ToOne’ 관계를 조회하고, 이 과정에서 얻은 식별자인 ‘orderId’를 이용하여 ‘ToMany’ 관계인 ‘OrderItem’을 한 번에 조회합니다. 이 과정에서 ‘MAP’을 사용함으로써 매칭 성능을 향상시킬 수 있습니다. 이는 ‘MAP’의 탐색 시간 복잡도가 O(1)이기 때문입니다.
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy::class) dataclassOrderQueryDto( val orderId: Long, val name: String, val orderDate: LocalDateTime, //주문시간 val orderStatus: OrderStatus, val address: Address, var orderItems: List<OrderItemQueryDto>? = null, )
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy::class) dataclassOrderFlatDto( val orderId: Long, val name: String, //주문시간 val orderDate: LocalDateTime, val orderStatus: OrderStatus, val address: Address, val itemName: String, val orderPrice: Int, val count: Int )
이 방식의 단점으로는, 쿼리는 한 번만 실행되지만, 조인 결과로 인해 데이터베이스에서 애플리케이션으로 전달되는 데이터에 중복이 추가될 수 있습니다. 이로 인해 상황에 따라서는 V5 방식보다 성능이 더 느려질 수 있습니다. 또한 애플리케이션에서 추가적인 작업량이 상당히 커집니다. 그리고 이 방식은 페이징이 불가능하다는 점도 단점으로 들 수 있습니다.