SPRING/INFLEARN

[๊ฐ•์˜] ์‹ค์ „! ์Šคํ”„๋ง ๋ถ€ํŠธ์™€ JPA ํ™œ์šฉ2 - API ๊ฐœ๋ฐœ๊ณผ ์„ฑ๋Šฅ ์ตœ์ ํ™” 2

ozllzL 2022. 8. 2. 03:38

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-API%EA%B0%9C%EB%B0%9C-%EC%84%B1%EB%8A%A5%EC%B5%9C%EC%A0%81%ED%99%94/dashboard

 

์‹ค์ „! ์Šคํ”„๋ง ๋ถ€ํŠธ์™€ JPA ํ™œ์šฉ2 - API ๊ฐœ๋ฐœ๊ณผ ์„ฑ๋Šฅ ์ตœ์ ํ™” - ์ธํ”„๋Ÿฐ | ๊ฐ•์˜

์Šคํ”„๋ง ๋ถ€ํŠธ์™€ JPA๋ฅผ ํ™œ์šฉํ•ด์„œ API๋ฅผ ๊ฐœ๋ฐœํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  JPA ๊ทนํ•œ์˜ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•์„ ํ•™์Šตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค., - ๊ฐ•์˜ ์†Œ๊ฐœ | ์ธํ”„๋Ÿฐ...

www.inflearn.com

4. API ๊ฐœ๋ฐœ ๊ณ ๊ธ‰ - ์ปฌ๋ ‰์…˜ ์กฐํšŒ ์ตœ์ ํ™”

- ์ฃผ๋ฌธ ์กฐํšŒ V1: ์—”ํ‹ฐํ‹ฐ ์ง์ ‘ ๋…ธ์ถœ

@RestController
@RequiredArgsConstructor
public class OrderApiController {

    private final OrderRepository orderRepository;

    @GetMapping("/api/v1/orders")
    public List<Order> ordersV1(){
        List<Order> all = orderRepository.findAllByString(new OrderSearch());
        for (Order order : all) {
            order.getMember().getName();
            order.getDelivery().getAddress();
            List<OrderItem> orderItems = order.getOrderItems();
            orderItems.forEach(o -> o.getItem().getName());
        }
        return all;
    }
}

์ „๋ถ€ lazy๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋ก์‹œ๋ฅผ ๊ฐ•์ œ ์ดˆ๊ธฐํ™” ํ•œ ํ›„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฟŒ๋ฆฌ๊ฒŒ ํ•จ

๊ฒฐ๊ตญ ์—”ํ‹ฐํ‹ฐ ์ง์ ‘ ๋…ธ์ถœ์ด๋ผ ๋ณ„๋กœ

 

- ์ฃผ๋ฌธ ์กฐํšŒ V2: ์—”ํ‹ฐํ‹ฐ๋ฅผ DTO๋กœ ๋ณ€ํ™˜

@RestController
@RequiredArgsConstructor
public class OrderApiController {

    /*์ƒ๋žต*/

    @GetMapping("/api/v2/orders")
    public List<OrderDto> orderV2(){
        List<Order> orders = orderRepository.findAllByString(new OrderSearch());
        List<OrderDto> collect = orders.stream()
                .map(o-> new OrderDto(o))
                .collect(Collectors.toList());

        return collect;
    }

    //@Data ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์œผ๋ฉด ์• ๋งคํ•ด์„œ ์•ˆ์“ฐ๋Š” ๊ฒŒ ๋‚˜์„ ์ˆ˜๋„ ์žˆ์Œ
    @Getter
    static class OrderDto {
        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;
        private List<OrderItemDto> orderItems;
        //๋†“์น˜๊ธฐ ์œ„ํ•œ ์‹ค์ˆ˜ : OrderItem๋„ DTO๋กœ ๋ฐ”๊ฟ”์„œ ๋ณด๋‚ด์•ผํ•จ
        //private List<OrderItem> orderItems;

        public OrderDto(Order order){
            orderId = order.getId();
            name = order.getMember().getName();
            orderDate = order.getOrderDate();
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getMember().getAddress();
            /*//์ด๊ฑฐ ์•ˆ์“ฐ๋ฉด lazy๋ผ ์•„๋ฌด๊ฒƒ๋„ ๋‚˜์˜ค์ง€ ์•Š์Œ
            order.getOrderItems().stream().forEach(o -> o.getItem().getName());
            orderItems = order.getOrderItems();*/
            //-> OrderItem๋„ Dto๋กœ ๋ณ€๊ฒฝ
            orderItems = order.getOrderItems().stream()
                    .map(orderItem -> new OrderItemDto(orderItem))
                    .collect(Collectors.toList());

        }

    }

    @Getter
    static class OrderItemDto{
        private String itemName;
        private int orderPrice;
        private int count;

        public OrderItemDto(OrderItem orderItem){
            itemName = orderItem.getItem().getName();
            orderPrice = orderItem.getOrderPrice();
            count = orderItem.getCount();
        }
    }
}

์›ํ•˜๋Š” ์ •๋ณด๋งŒ ๋ณด์ด๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

์ง€์—ฐ ๋กœ๋”ฉ์ด ๋งŽ์•„์„œ

Order ์กฐํšŒ์— ๋‘๊ฐœ๋‚˜์˜ด

๊ฐ Order๋งˆ๋‹ค

๋ฉค๋ฒ„ ํ•œ ๋ช… ์กฐํšŒ

delivery ์ •๋ณด

Items ๋‘๊ฐœ ์กฐํšŒ

Items๋งˆ๋‹ค Item ์กฐํšŒ์— 2๋ฒˆ์”ฉ

...

SQL ์ฟผ๋ฆฌ๊ฐ€ ์กฐ์ง€๊ฒŒ ๋งŽ์ด ๋‚˜๊ฐ

๋”ฑ ๋ด๋„ ์„ฑ๋Šฅ ๋š ๋–จ์–ด์ ธ ๋ณด์ž„

 

- ์ฃผ๋ฌธ ์กฐํšŒ V3: ์—”ํ‹ฐํ‹ฐ๋ฅผ DTO๋กœ ๋ณ€ํ™˜ - ํŒจ์น˜ ์กฐ์ธ ์ตœ์ ํ™”

@Repository
@RequiredArgsConstructor
public class OrderRepository {

    /*์ƒ๋žต*/

    public List<Order> findAllWithItem() {
        return em.createQuery(
                "select o from Order o" +
                        " join fetch o.member m" +
                        " join fetch o.delivery d" +
                        " join fetch o.orderItems oi" +
                        " join fetch oi.item i", Order.class)
                .getResultList();
    }
}


DB์—์„œ ์ผ๋Œ€๋‹ค์ผ๋•Œ ๋‹ค์— ๋งž์ถฐ์„œ ๋ฆฌํ„ดํ•˜๊ธฐ ๋•Œ๋ฌธ์— order 4๊ฐœ๊ฐ€ ๋ฆฌํ„ด๋œ๋‹ค -> distinct๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด 2๊ฐœ ๋ฆฌํ„ด
์‚ฌ์‹ค ์ฟผ๋ฆฌ์—์„œ๋Š” ์™„์ „ํžˆ ๋˜‘๊ฐ™์•„์•ผ ์ค‘๋ณต์ œ๊ฑฐ์ด๋ฏ€๋กœ, ์—ฌ์ „ํžˆ 4๊ฐœ๊ฐ€ ๋‚˜์˜ค์ง€๋งŒ,
JPA์—์„œ ์ž์ฒด์ ์œผ๋กœ distinct๋ฅผ ๋ณด๋ฉด ์ค‘๋ณต์„ ๊ฑธ๋Ÿฌ์„œ ์ปฌ๋ ‰์…˜์— ๋‹ด์•„์ค€๋‹ค.

distinct๊ฐ€ ์ค‘๋ณต ์กฐํšŒ๋ฅผ ๋ง‰์•„์ค€๋‹ค

@RestController
@RequiredArgsConstructor
public class OrderApiController {
	/*์ƒ๋žต*/
    @GetMapping("/api/v3/orders")
    public List<OrderDto> orderV3(){
        List<Order> orders = orderRepository.findAllWithItem();

        List<OrderDto> collect = orders.stream()
                .map(o-> new OrderDto(o))
                .collect(Collectors.toList());

        return collect;
	/*์ƒ๋žต*/
    }
}

ํŒจ์น˜ ์กฐ์ธ์œผ๋กœ SQL์ด ํ•œ ๋ฒˆ๋ฐ˜ ์‹คํ–‰๋จ

 

ํ•˜์ง€๋งŒ ๋‹จ์ , ์ผ๋Œ€๋‹ค๋ฅผ ํ•˜๋Š” ์ˆœ๊ฐ„ ํŽ˜์ด์ง•์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค

.setFirstResult(1), .setMaxResults(100) : 1๋ถ€ํ„ฐ 100๊ฐœ ๊ฐ€์ ธ์˜ด

WARN : firstResult/maxResults sepcified with collection fetch; applying in memory

Distinct๋œ ๊ฒฐ๊ณผ ๊ธฐ์ค€์ด ์•„๋‹Œ, ๋ปฅํŠ€๊ธฐ ๋œ ์ƒํƒœ, order item์„ ๊ธฐ์ค€์œผ๋กœ ์ ์šฉ์ด ๋จ

์ ์ ˆํ•œ ๊ฐœ์ˆ˜๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์–ด ๋งค์šฐ ์œ„ํ—˜ํ•จ(ex) OutOfMemory), ํŽ˜์ด์ง•์ด ๋ถˆ๊ฐ€๋Šฅํ•จ

 

๋˜ํ•œ ์ปฌ๋ ‰์…˜ ํŒจ์น˜ ์กฐ์ธ์€ ํ•œ ๋ฒˆ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‘๊ฐœ ์ด์ƒ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ๋ปฅํŠ€๊ธฐ์— ๋ปฅํŠ€๊ธฐ ํ•ด์„œ ํ˜ผ๋ž€์„ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์Œ

 

- ์ฃผ๋ฌธ ์กฐํšŒ V3.1: ์—”ํ‹ฐํ‹ฐ๋ฅผ DTO๋กœ ๋ณ€ํ™˜ - ํŽ˜์ด์ง•๊ณผ ํ•œ๊ณ„ ๋ŒํŒŒ

@Repository
@RequiredArgsConstructor
public class OrderRepository {
	public List<Order> findAllWithMemberDelivery(int offset, int limit) {
        return em.createQuery(
                "select o from Order o" +
                        " join fetch o.member m" +
                        " join fetch o.delivery d", Order.class)
                .setFirstResult(offset)
                .setMaxResults(limit)
                .getResultList();
    }
@RestController
@RequiredArgsConstructor
public class OrderApiController {

    /*์ƒ๋žต*/

    @GetMapping("/api/v3/orders")
    public List<OrderDto> orderV3(){
        List<Order> orders = orderRepository.findAllWithItem();

        List<OrderDto> collect = orders.stream()
                .map(o-> new OrderDto(o))
                .collect(Collectors.toList());

        return collect;
    }

    @GetMapping("/api/v3.1/orders")
    public List<OrderDto> orderV3_page(
            @RequestParam(value = "offset" , defaultValue = 0) int offset,
            @RequestParam(value = "limit" , defaultValue = 100) int limit){
        //ToOne ๊ด€๊ณ„๊นŒ์ง€๋งŒ ๋ญ‰ํƒฑ์ด๋กœ ํ•œ๋ฒˆ์— ๊ฐ€์ ธ์˜จ๋‹ค
        List<Order> orders = orderRepository.findAllWithMemberDelivery(offset, limit);
        // ์—ฌ๊ธฐ๊นŒ์ง€๋งŒ ํ•˜๋ฉด 1 + orders ๋ฃจํ”„ (2๋ฒˆ + 1) * (orderItems 1 + orderitem 2  * item 2)... ๊ณ„์‚ฐ ๋‹ค์Œ์— ๋งˆ์ € ์ผ๋‹จ ์—ด๋ผ ๋งŽ์Œ

        List<OrderDto> collect = orders.stream()
                .map(o-> new OrderDto(o))
                .collect(Collectors.toList());

        return collect;
    }
    /*์ƒ๋žต*/
}

jpa.hibernate.properties.default_batch_fetch_size: 100  ์ถ”๊ฐ€

 

100์˜ ์˜๋ฏธ๋Š” inquery์˜ ๊ฐœ์ˆ˜๋ฅผ ๋ช‡๊ฐœ๋กœ ํ•  ๊ฒƒ์ธ๊ฐ€

ํ•œ ๋ฐฉ์— 100๊ฐœ๋ฅผ ๋‹ค ๋•ก๊ฒจ์˜จ๋‹ค

์‚ฌ์ด์ฆˆ : ๊ณ ๊ณ ์ต์„  ํ•˜์ง€๋งŒ ์ˆœ๊ฐ„ ๋ถ€ํ•˜๋ฅผ ๊ฒฌ๋”œ ์ˆ˜ ์žˆ์–ด์•ผํ•จ ์ฃผ๋กœ 100~1000 ์‚ฌ์ด

1 + N + N๋ฌธ์ œ ํ•ด๊ฒฐ

 

๋ฐ์ดํ„ฐ ์–‘์ด ์—„์ฒญ ๋งŽ์•„์ง€๋ฉด ์˜คํžˆ๋ ค ํ•œ๋ฒˆ์— ์ „๋ถ€ joinํ•ด ๊ฐ€์ ธ์˜ค๋Š” ์œ— ๋ฐฉ๋ฒ•๋ณด๋‹ค ์ด ๋ฐฉ๋ฒ•์ด ๋” ์ข‹์„ ์ˆ˜ ๋„ ์žˆ๋‹ค

๊ฒฐ๋ก  ์žฅ์  : 

์ฟผ๋ฆฌ ํ˜ธ์ถœ ์ˆ˜๊ฐ€ 1 + N -> 1 + 1 ๋กœ ์ตœ์ ํ™”
์กฐ์ธ๋ณด๋‹ค DB ๋ฐ์ดํ„ฐ ์ „์†ก๋Ÿ‰์ด ์ตœ์ ํ™” , ์ค‘๋ณต๋œ ๋ฐ์ดํ„ฐ ์ „์†ก ์•ˆํ•ด๋„ ๋จ

ํŒจ์น˜ ์กฐ์ธ ๋ฐฉ์‹๊ณผ ๋น„๊ตํ•ด์„œ ์ฟผ๋ฆฌ ํ˜ธ์ถœ ์ˆ˜๊ฐ€ ์•ฝ๊ฐ„ ์ฆ๊ฐ€ํ•˜์ง€๋งŒ, DB ๋ฐ์ดํ„ฐ ์ „์†ก๋Ÿ‰์€ ๊ฐ์†Œ
์ปฌ๋ ‰์…˜ ํŽ˜์น˜ ์กฐ์ธ์€ ํŽ˜์ด์ง• ๋ถˆ๊ฐ€๋Šฅ ํ•˜์ง€๋งŒ ์ด ๋ฐฉ๋ฒ•์€ ํŽ˜์ด์ง• ๊ฐ€๋Šฅ

 

- ์ฃผ๋ฌธ ์กฐํšŒ V4: JPA์—์„œ DTO ์ง์ ‘ ์กฐํšŒ

repository ์ƒˆ๋กœ ์ƒ์„ฑ

@Repository
@RequiredArgsConstructor
public class OrderQueryRepository {
    private final EntityManager em;

    public List<OrderQueryDto> findOrderQueryDtos(){

        //์ปฌ๋ ‰์…˜์„ ๋ฐ”๋กœ ๋„ฃ์„ ์ˆ˜๋Š” ์—†์Œ
        List<OrderQueryDto> result = findOrders();

        //์ปฌ๋ ‰์…˜์„ ์ง์ ‘ ์ฑ„์šด๋‹ค
        result.forEach(o ->{
            List<OrderItemQueryDto> orderItems = findOrderItems(o.getOrderId());
            o.setOrderItems(orderItems);
        });
        return result;
    }

    private List<OrderItemQueryDto> findOrderItems(Long orderId){
        return em.createQuery("select new jpabook.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)
                .setParameter("orderId", orderId)
                .getResultList();
    }

    private List<OrderQueryDto> findOrders() {
        return em.createQuery("select new jpabook.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)
                .getResultList();
    }
}

 

@RestController
@RequiredArgsConstructor
public class OrderApiController {
    /*์ƒ๋žต*/
	@GetMapping("/api/v4/orders")
	public List<OrderQueryDto> orderV4(){
    	return orderQueryRepository.findOrderQueryDtos();
	}
    /*์ƒ๋žต*/
}

DTO ์ƒ์„ฑ

@Data
public class OrderQueryDto {
    private Long orderId;
    private String name;
    private LocalDateTime orderDate;
    private OrderStatus orderStatus;
    private Address address;
    private List<OrderItemQueryDto> orderItems;

    public OrderQueryDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address) {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
    }
}
@Data
public class OrderItemQueryDto {

    @JsonIgnore
    private Long orderId;
    private String itemName;

    private int orderPrice;
    private int count;

    public OrderItemQueryDto(Long orderId, String itemName, int orderPrice, int count) {
        this.orderId = orderId;
        this.itemName = itemName;
        this.orderPrice = orderPrice;
        this.count = count;
    }
}

๊ฒฐ๊ณผ๋Š” ๋ญ... ๋น„์Šท

 

row ์ˆ˜๊ฐ€ ์ฆ๊ฐ€ํ•˜์ง€ ์•Š๋Š” ToOne ๊ด€๊ณ„๋Š” ์กฐ์ธ์œผ๋กœ ์ตœ์ ํ™” ํ•˜๊ธฐ ์‰ฌ์šฐ๋ฏ€๋กœ ํ•œ๋ฒˆ์— ์กฐํšŒํ•˜๊ณ , ToMany
๊ด€๊ณ„๋Š” ์ตœ์ ํ™” ํ•˜๊ธฐ ์–ด๋ ค์šฐ๋ฏ€๋กœ findOrderItems() ๊ฐ™์€ ๋ณ„๋„์˜ ๋ฉ”์„œ๋“œ๋กœ ์กฐํšŒํ•œ๋‹ค.

๋ณ„๋„์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์“ฐ๋Š” ๊ณผ์ •์—์„œ N+1๋ฒˆ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๋Š” ๋‹จ์ ์ด ์žˆ์Œ

 

- ์ฃผ๋ฌธ ์กฐํšŒ V5: JPA์—์„œ DTO ์ง์ ‘ ์กฐํšŒ - ์ปฌ๋ ‰์…˜ ์กฐํšŒ ์ตœ์ ํ™”

@RestController
@RequiredArgsConstructor
public class OrderApiController {
    /*์ƒ๋žต*/
	@GetMapping("/api/v5/orders")
    public List<OrderQueryDto> orderV5(){
        return orderQueryRepository.findAllByDto_optimization();
    }
    /*์ƒ๋žต*/
}
@Repository
@RequiredArgsConstructor
public class OrderQueryRepository {
    public List<OrderQueryDto> findAllByDto_optimization(){
        List<OrderQueryDto> result = findOrders();

        List<Long> orderIds = result.stream().map(o -> o.getOrderId())
                .collect(Collectors.toList());

        List<OrderItemQueryDto> orderItems = em.createQuery("select new jpabook.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 in :orderIds", OrderItemQueryDto.class)
                .setParameter("orderIds", orderIds)
                .getResultList();

        //์‚ฌ์šฉํ•˜๊ธฐ ํŽธํ•˜๊ฒŒ ๋งต ํ˜•์‹์œผ๋กœ ๋ณ€๊ฒฝ
        Map<Long, List<OrderItemQueryDto>> orderItemMap = orderItems.stream()
                .collect(Collectors.groupingBy(orderItemQueryDto -> orderItemQueryDto.getOrderId()));

        result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId())));

        return result;

    }
}

์ฟผ๋ฆฌ ๋‘ ๋ฒˆ์œผ๋กœ ์ตœ์ ํ™” ๊ฐ€๋Šฅ

๋ฃจํŠธ 1๋ฒˆ, ์ปฌ๋ ‰์…˜ 1๋ฒˆ

Map์„ ์‚ฌ์šฉํ•ด์„œ ๋งค์นญ ์„ฑ๋Šฅ ํ–ฅ์ƒ

 

- ์ฃผ๋ฌธ ์กฐํšŒ V6: JPA์—์„œ DTO ์ง์ ‘ ์กฐํšŒ, ํ”Œ๋žซ ๋ฐ์ดํ„ฐ ์ตœ์ ํ™”

์ฟผ๋ฆฌ ํ•œ๋ฒˆ์œผ๋กœ ํ•ด๊ฒฐ ๊ฐ€๋Šฅ

@RestController
@RequiredArgsConstructor
public class OrderApiController {
    /*์ƒ๋žต*/
	//์ฟผ๋ฆฌ๊ฐ€ ํ•œ ๋ฒˆ๋งŒ ๋‚˜๊ฐ
    //ํ•˜์ง€๋งŒ join์„ ์ด์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์— ๋งž์ถฐ์„œ ๋ฟ”๋ ค์ ธ ๋‚˜์˜ด
    //๋”ฐ๋ผ์„œ ํŽ˜์ด์ง•์„ ๋ชปํ•จ
    //API ์ŠคํŽ™์ด ์•ˆ๋งž๋Š” ๋ฌธ์ œ๋Š” ๊ทธ๋ƒฅ ๋…ธ๊ฐ€๋‹ค๋กœ ๋ฐœ๋ผ๋‚ด๋Š” ์ฝ”๋“œ๋ฅผ ์งœ๋ฉด ๋จ
    @GetMapping("/api/v6/orders")
    public List<OrderFlatDto> orderV6(){
        return orderQueryRepository.findAllByDto_flat();
    }
    /*์ƒ๋žต*/
}
package jpabook.jpashop.repository.order.query;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Repository
@RequiredArgsConstructor
public class OrderQueryRepository {
    /*์ƒ๋žต*/

    public List<OrderFlatDto> findAllByDto_flat() {
        return em.createQuery(
                "select new jpabook.jpashop.repository.order.query.OrderFlatDto(o.id, m.name, o.orderDate, o.status, d.address, i.name, oi.orderPrice, oi.count)" +
                        " from Order o" +
                        " join o.member m" +
                        " join o.delivery d" +
                        " join o.orderItems oi" +
                        " join oi.item i", OrderFlatDto.class)
                .getResultList();
    }
}

์ง์ ‘ ์ •๋ฆฌํ•˜๋Š” ์ฝ”๋“œ(์ •๋ฆฌ ์•ˆ๋˜์–ด์žˆ์Œ, ๊ทธ๋ƒฅ ๋ณต๋ถ™)

๋”๋ณด๊ธฐ

return flats.stream()
.collect(groupingBy(o -> new OrderQueryDto(o.getOrderId(),
o.getName(), o.getOrderDate(), o.getOrderStatus(), o.getAddress()),
mapping(o -> new OrderItemQueryDto(o.getOrderId(),
o.getItemName(), o.getOrderPrice(), o.getCount()), toList())
)).entrySet().stream()
.map(e -> new OrderQueryDto(e.getKey().getOrderId(),
e.getKey().getName(), e.getKey().getOrderDate(), e.getKey().getOrderStatus(),
e.getKey().getAddress(), e.getValue()))
.collect(toList());

 

๊ทธ๋ฆฌ๊ณ  OrderQueryDto์— @EqualsAndHashCode(of = orderId) ์ถ”๊ฐ€

orderId ๊ธฐ์ค€์œผ๋กœ ๋ฌถ์–ด์คŒ

๋‹จ :

์ฟผ๋ฆฌ๋Š” ํ•œ๋ฒˆ์ด์ง€๋งŒ ์กฐ์ธ์œผ๋กœ ์ธํ•ด DB์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ „๋‹ฌํ•˜๋Š” ๋ฐ์ดํ„ฐ์— ์ค‘๋ณต ๋ฐ์ดํ„ฐ๊ฐ€ ์ถ”๊ฐ€

-> V5 ๋ณด๋‹ค ๋” ๋А๋ฆด ์ˆ˜ ๋„
์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ถ”๊ฐ€ ์ž‘์—…์ด ํผ
ํŽ˜์ด์ง• ๋ถˆ๊ฐ€๋Šฅ

 

- API ๊ฐœ๋ฐœ ๊ณ ๊ธ‰ ์ •๋ฆฌ

 

์—”ํ‹ฐํ‹ฐ ์กฐํšŒ

  • ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•ด์„œ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜: V1
  • ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ํ›„ DTO๋กœ ๋ณ€ํ™˜: V2
  • ํŽ˜์น˜ ์กฐ์ธ์œผ๋กœ ์ฟผ๋ฆฌ ์ˆ˜ ์ตœ์ ํ™”: V3
  • ์ปฌ๋ ‰์…˜ ํŽ˜์ด์ง•๊ณผ ํ•œ๊ณ„ ๋ŒํŒŒ: V3.1
    ์ปฌ๋ ‰์…˜์€ ํŽ˜์น˜ ์กฐ์ธ์‹œ ํŽ˜์ด์ง•์ด ๋ถˆ๊ฐ€๋Šฅ
    ToOne ๊ด€๊ณ„๋Š” ํŽ˜์น˜ ์กฐ์ธ์œผ๋กœ ์ฟผ๋ฆฌ ์ˆ˜ ์ตœ์ ํ™”
    ์ปฌ๋ ‰์…˜์€ ํŽ˜์น˜ ์กฐ์ธ ๋Œ€์‹ ์— ์ง€์—ฐ ๋กœ๋”ฉ์„ ์œ ์ง€ํ•˜๊ณ , hibernate.default_batch_fetch_size ,
    @BatchSize ๋กœ ์ตœ์ ํ™”\

 

DTO ์ง์ ‘ ์กฐํšŒ

  • JPA์—์„œ DTO๋ฅผ ์ง์ ‘ ์กฐํšŒ: V4
  • ์ปฌ๋ ‰์…˜ ์กฐํšŒ ์ตœ์ ํ™” - ์ผ๋Œ€๋‹ค ๊ด€๊ณ„์ธ ์ปฌ๋ ‰์…˜์€ IN ์ ˆ์„ ํ™œ์šฉํ•ด์„œ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฏธ๋ฆฌ ์กฐํšŒํ•ด์„œ ์ตœ์ ํ™”: V5
  • ํ”Œ๋žซ ๋ฐ์ดํ„ฐ ์ตœ์ ํ™” - JOIN ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋Œ€๋กœ ์กฐํšŒ ํ›„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์›ํ•˜๋Š” ๋ชจ์–‘์œผ๋กœ ์ง์ ‘ ๋ณ€ํ™˜: V6

๊ถŒ์žฅ ์ˆœ์„œ
1. ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ๋ฐฉ์‹์œผ๋กœ ์šฐ์„  ์ ‘๊ทผ
    1. ํŽ˜์น˜์กฐ์ธ์œผ๋กœ ์ฟผ๋ฆฌ ์ˆ˜๋ฅผ ์ตœ์ ํ™”
    2. ์ปฌ๋ ‰์…˜ ์ตœ์ ํ™”
        1. ํŽ˜์ด์ง• ํ•„์š” hibernate.default_batch_fetch_size , @BatchSize ๋กœ ์ตœ์ ํ™”
        2. ํŽ˜์ด์ง• ํ•„์š”X ํŽ˜์น˜ ์กฐ์ธ ์‚ฌ์šฉ
2. ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐ์ด ์•ˆ๋˜๋ฉด DTO ์กฐํšŒ ๋ฐฉ์‹ ์‚ฌ์šฉ
3. DTO ์กฐํšŒ ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐ์ด ์•ˆ๋˜๋ฉด NativeSQL or ์Šคํ”„๋ง JdbcTemplate

 

5. API ๊ฐœ๋ฐœ ๊ณ ๊ธ‰ - ์‹ค๋ฌด ํ•„์ˆ˜ ์ตœ์ ํ™”

- OSIV์™€ ์„ฑ๋Šฅ ์ตœ์ ํ™”