개발놀이터

JPA를 활용한 (XToOne) Restful API설계 본문

JPA/JPA

JPA를 활용한 (XToOne) Restful API설계

마늘냄새폴폴 2021. 9. 13. 23:16

*Restful API 설계

앞으로 보여주는 예제들은 XToOne (ManyTonOne, OneToOne) 인 상황에서의 성능 최적화를 다루고 있다.

*version1 = 응답 값으로 엔티티를 직접 외부에 노출함

@GetMapping("/api/v1/simple-orders")
    public List<Order> orderV1() {
        List<Order> all = orderRepository.findAll(new OrderSearch());

        return all;
    }



문제점
1. 엔티티에 프레젠테이션 계층을 위한 로직이 추가된다.
2. 기본적으로 엔티티의 모든 값이 노출된다.
3. 응답 스펙을 맞추기 위한 로직이 추가된다. (@JsonIgnore, 별도에 뷰 로직 등등)
4. 실무에서는 같은 엔티티에 대해 API가 용도에 따라 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 프레젠테이션 응답 로직을 담기는 어렵다.
5. 엔티티가 변경되면 API스펙이 변한다.
6. 추가로 컬렉션을 직접 반환하면 향후 API 스펙을 변경하기 어렵다.


*version2 = 엔티티를 DTO로 변환

@GetMapping("/api/v2/simple-orders")
    public List<SimpleOrderDto> orderV2() {
        List<Order> findAll = orderRepository.findAll(new OrderSearch());

        return findAll.stream().map(o -> new SimpleOrderDto(o)).collect(Collectors.toList());
    }



@Data
    public class SimpleOrderDto {
        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;


        public SimpleOrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName(); //LAZY 초기화
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress(); //LAZY 초기화
        }
    }



문제점
1. API를 유연하게 사용하지 못한다. 위의 방법대로 return 하면 json의 시작이 대괄호([] : 배열)이다. 대괄호가 되면 이 이상의 API를 추가할 수 없다. 예를들어서 해당 API를 사용하는 곳에서 member의 개수를 세어달라는 요청사항이 들어오게 되면 count라는 값을 추가해야 될텐데 대괄호(배열)는 값을 추가할 수 없다. 이럴 때를 대비해서 DTO를 Result에 한번 감싸서 return을 해야한다. 

@Data
    @AllArgsConstructor
    static class Result<T> {
        private T data;
    }


이렇게 Result객체를 하나 만들어 두고

@GetMapping("/api/v2/simple-orders")
    public Result orderV2() {
        List<Order> findAll = orderRepository.findAll(new OrderSearch());

        List<Order> collect = findAll.stream().map(o -> new SimpleOrderDto(o)).collect(Collectors.toList());
       
        return Result(collect);
    }


이렇게 한번 감싸서 return 하게 되면 json의 시작이 중괄호({})로 바뀐다. 이런 상황에서는 추가 요청사항이 있으면 유연하게 대처할 수 있다. 

2. fetchType을 LAZY로 설정해야 하기 때문에 프록시가 초기화 될때마다 해당 쿼리를 날려서 N + 1 문제를 야기한다. 1은 내가 날린 최초의 쿼리이고 N은 최초의 쿼리로부터 나온 결과값의 개수이다. 만약 Order데이터가 2개가 들어있다면 N은 2가 되는것이고 위의 예제처럼 초기화가 두번 일어나는 경우 N이 하나 더 늘어 N + N + 1문제로 변한다(최악의 경우) 


*version3

@GetMapping("/api/v3/simple-orders")
    public List<SimpleOrderDto> orderV3() {
        List<Order> orders = orderRepository.findAllWithMemberDelivery();

        return orders.stream().map(o -> new SimpleOrderDto(o)).collect(Collectors.toList());
    }



public List<Order> findAllWithMemberDelivery() {
        return em.createQuery("select o from Order o join fetch o.member m join fetch o.delivery", Order.class)
                .getResultList();
    }


이렇게 내가 필요한 객체를 페치조인을 이용하여 한번에 끌어오게 되면 대부분의 성능문제가 해결이 된다. 

'JPA > JPA' 카테고리의 다른 글

AuditorAware  (0) 2021.09.23
Spring Data JPA  (0) 2021.09.23
JPQL 중급  (0) 2021.08.26
JPQL 초급  (0) 2021.08.26
값 타입  (0) 2021.08.26