[스프링 JPA 활용] WEEK 10

enxnong·2023년 11월 11일
0

김영환님의 강의 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화 보면서 공부한 내용입니다.

🏊‍♀️ 섹션 1

회원 등록 API

📝 V1

@RestController // @Controller + @ResponseBody 를 합친 어노테이션
// => 데이터 자체를 json이나 xml으로 바로 보내자라는 의미
@RequiredArgsConstructor
public class MemberApiController {

    private final MemberService memberService;

    @PostMapping("/api/v1/members")
    public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member){
        // api만들 때 항상 엔티티를 파라미터로 받지 말기 + 웹에 노출해서도 안됨
        // => api를 위한 별도의 DTO를 만들어야함
        return new CreateMemberResponse(id);
        Long id = memberService.join(member);
    }

    @Data
    static class CreateMemberResponse{
        private Long id;

        public CreateMemberResponse(Long id) {
            this.id = id;
        }
    }

}

✅ POST MAN에서 조회하는 법

📝 V2 (정석)

  • 엔티티의 값이 바껴도 api가 영향을 받지 않음
  • api가 받는 값을 DTO만 보면 정확하게 알 수 있음
    @PostMapping("/api/v2/members")
    public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {

        Member member = new Member();
        member.setName(request.getName());

        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }
    
    @Data
    static class CreateMemberRequest { // DTO
        private String name;
    }    

회원 수정 API

  @PutMapping("/api/v2/members/{id}")
    public UpdateMemberResponse updateMemberV2(
            @PathVariable("id") Long id, // 아이디 넘어오고
            @RequestBody @Valid UpdateMemberRequest request){ // 업데이트 할 이름 넘어옴

        memberService.update(id, request.getName());
        Member findMember = memberService.findOne(id);
        return new UpdateMemberResponse(findMember.getId(), findMember.getName());
    }

    @Data
    static class UpdateMemberRequest{
        private String name;
    }

    @Data
    @AllArgsConstructor // 모든 파라미터를 넘기는 생성자가 필요함
    static class UpdateMemberResponse{
        private Long id;
        private String name;
    }

📝 결과

회원 조회 API

📝 V1

    @GetMapping("/api/v1/members")
    public List<Member> membersV1(){
        return memberService.findAll();
    }
    
    // 엔티티를 직접 노출하게 되면 엔티티에 있는 정보들이 다 외부에 노출이 됨 

✅ 회원정보만 조회하고 싶은데 orders도 함께 조회됨
즉, 기본적으로 엔티티의 모든 값이 노출됨
✅ 실무에서는 같은 엔티티에 대해 api가 용도에 따라 다양하게 만든어지는데, 한 엔티티에 각각의 api를 위한 프레젠테이션 응답 로직을 담기 어려움
✅ 엔티티가 변경되면 api 스펙이 변함

📝 V2 (정석)

    @GetMapping("/api/v2/members")
    public Result MemberV2(){
        List<Member> members = memberService.findAll();
        List<Object> collect = members.stream()
                .map(m -> new MemberDto(m.getName()))
                .collect(Collectors.toList());

        return new Result(collect);
    }

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

    @Data
    @AllArgsConstructor
    static class MemberDto{
        private String name;
    }

✅ 노출하고 싶은 데이터만 노출 가능
✅ 엔티티가 변경되어도 api 스펙이 변하지 않음

🏊‍♀️ 섹션 3

간단한 주문 조회 V1

@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {
    private final OrderRepository orderRepository;

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

✅ 엔티티를 직접 노출할 때는 양방향 연관관계가 걸린 곳은 꼭 한 곳을 @JsonIgnore처리 해야한다. 아니면 무한 루프 발생함
✅ 엔티티를 API응답으로 외부로 노출하는 것은 좋지 않으므로 DTO로 변환해서 반환하는 것이 더 좋다
✅ 즉시 로딩으로 설정하면 연관관계가 필요없는 경우에도 데이터를 항상 조회해서 성능 문제가 발생할 수 있다. 항상 지연 로딩을 기본으로 하고, 성능 최적화가 필요한 경우에는 페치 조인(fetch join)을 사용해라

간단한 주문 조회 V2

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

        return result;
    }

    @Data
    static 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 초기화 => getMember() 까지는 프록시이지만 
            // getName()을 가져오는 순간 실제 name을 끌고와야되기 때문)
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress(); // Lazy 초기화
        }
    }

profile
높은 곳을 향해서

0개의 댓글