WEB JAVA SPRING/PROJECT

[Day-15] rest api 엔티티 직접 노출

sshhhh 2023. 8. 28. 10:51

조회용 샘플 데이터 입력

 

InitDb

package jpabook.jpashop;

import jpabook.jpashop.domain.*;
import jpabook.jpashop.domain.item.Book;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;

/**
 * <주문 2개>
 * userA
 * JPA1 BOOK
 * JPA2 BOOK
 * userB
 * SPRING1 BOOK
 * SPRING2 BOOK
 */
@Component //스프링빈 등록
@RequiredArgsConstructor
public class InitDb {


    private final InitService initService;

    @PostConstruct
    public void init(){
        initService.dbInit1();
        initService.dbInit2();
    }


    @Component
    @Transactional
    @RequiredArgsConstructor
    static class InitService{

        private final EntityManager em;

        public void dbInit1(){
            //회원등록

            Member member = createMember("userA", "서울", "1", "2000");
            em.persist(member);

            //상품등록
            Book book1 = createBook("JPA1 BOOK", 10000, 545);
            em.persist(book1);


            Book book2 = createBook("JPA2 BOOK", 5000, 888);
            em.persist(book2);

            OrderItem orderItem1 = OrderItem.createOrderItem(book1,10000,1);
            OrderItem orderItem2 = OrderItem.createOrderItem(book2,5000,4);

            Delivery delivery = createDelivery(member);
            Order order = Order.createOrder(member, delivery,orderItem1,orderItem2); //주문내역
            em.persist(order);

        }

        private Delivery createDelivery(Member member) {
            Delivery delivery = new Delivery();
            delivery.setAddress(member.getAddress());
            return delivery;
        }

        private Book createBook(String name, int price, int stockQuantity) {
            Book book1 =new Book();
            book1.setName(name);
            book1.setPrice(price);
            book1.setStockQuantity(stockQuantity); //ctrl+alt+p
            return book1;
        }

        //ctrl+alt+m
        private Member createMember(String name, String city, String street, String zipcode) {
            Member member = new Member();
            member.setName(name);
            member.setAddress(new Address(city, street, zipcode));
            return member;
        }

        public void dbInit2(){
            //회원등록
            Member member = createMember("userB", "진주", "2", "22222");
            em.persist(member);

            //상품등록
            Book book1 = createBook("SPRING1 BOOK", 20000, 200);
            em.persist(book1);


            Book book2 = createBook("SPRING2 BOOK", 40000, 400);
            em.persist(book2);

            OrderItem orderItem1 = OrderItem.createOrderItem(book1,20000,3);
            OrderItem orderItem2 = OrderItem.createOrderItem(book2,40000,5);

            Delivery delivery = createDelivery(member);
            Order order = Order.createOrder(member, delivery,orderItem1,orderItem2); //주문내역
            em.persist(order);

        }

    }
}

 

 


rest api 엔티티 직접 노출

 

 

<엔티티 직접 노출>

 

오류발생

http://localhost:8080/api/v1/simple-orders
simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor
 
 
 
지연로딩 하이버네이트로 수정 후 결과 
 
엔티티를 직접 노출했더니 이렇게  필요하지 않은 정보(orderitems)까지 다 가져옴
 {
        "id": 4,
        "member": {
            "id": 1,
            "name": "userA",
            "address": {
                "city": "서울",
                "street": "1",
                "zipcode": "2000"
            }
        },
        "orderItems": [
            {
                "id": 6,
                "item": {
                    "id": 2,
                    "name": "JPA1 BOOK",
                    "price": 10000,
                    "stockQuantity": 544,
                    "categories": [],
                    "author": null,
                    "isbn": null
                },
                "orderPrice": 10000,
                "count": 1,
                "totalPrice": 10000
            },
            {
                "id": 7,
                "item": {
                    "id": 3,
                    "name": "JPA2 BOOK",
                    "price": 5000,
                    "stockQuantity": 884,
                    "categories": [],
                    "author": null,
                    "isbn": null
                },
                "orderPrice": 5000,
                "count": 4,
                "totalPrice": 20000
            }
        ],
        "delivery": {
            "id": 5,
            "address": {
                "city": "서울",
                "street": "1",
                "zipcode": "2000"
            },
            "status": null
        },
        "orderDate": "2022-08-12T17:57:10.60266",
        "status": "ORDER",
        "totalPrice": 30000
    },

 

강제 지연로딩 설정 (엔티티 조회시점이 아닌 엔티티내 연관관계를 참조할때 sql 쿼리 나감)

{
        "id": 4,
        "member": {
            "id": 1,
            "name": "userA",
            "address": {
                "city": "서울",
                "street": "1",
                "zipcode": "2000"
            }
        },
        "orderItems": null,
        "delivery": {
            "id": 5,
            "address": {
                "city": "서울",
                "street": "1",
                "zipcode": "2000"
            },
            "status": null
        },
        "orderDate": "2022-08-12T17:58:48.708913",
        "status": "ORDER",
        "totalPrice": 30000
    },

하지만 이렇게 데이터를 너무 많이 노출하면 운영하기 힘들다...

 

> 주의: 엔티티를 직접 노출할 때는 양방향 연관관계가 걸린 곳은 꼭!  엔티티에서 한곳을 @JsonIgnore 처리 해야 한다.

  안그러면 양쪽을 서로 호출하면서 무한 루프가 걸린다.

 

> 참고: 앞에서 계속 강조했듯이 정말 간단한 애플리케이션이 아니면 엔티티를 API 응답으로 외부로 노출하는 것은
   좋지 않다. 따라서 Hibernate5Module 를 사용하기 보다는 DTO로 변환해서 반환하는 것이 더 좋은 방법이다

 

주의: 지연 로딩(LAZY)을 피하기 위해 즉시 로딩(EARGR)으로 설정하면 안된다!
즉시 로딩 때문에 연관관계가 필요 없는 경우에도 데이터를 항상 조회해서 성능 문제가 발생할 수 있다.
즉시 로딩으로 설정하면 성능 튜닝이 매우 어려워 진다.
> 항상 지연 로딩을 기본으로 하고, 성능 최적화가 필요한 경우에는 페치 조인(fetch join)을 사용해라!

 

 

 

OrderSimpleApiController

/**
 * 성능 최적화
 * xToOne(ManyToOne, OneToOne)
 * <Order>
 * Order -> Member - ManyToOne
 * Order -> Delivery - OneToOne
 */
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {

    private final OrderRepository orderRepository;

    /*V1. 엔티티 직접 노출
    - 양방향연관관계 참조하면 무한루프 -> @JsonIgnore
    order -> member , member -> orders 양방향 연관관계를 계속 로딩하게 된다.
    따라서 @JsonIgnore 옵션을 한곳에 주어야 한다

    - Hibernate5Module 모듈 등록, LAZY=null 처리

     */
    @GetMapping("/api/v1/simple-orders")
    public List<Order> ordersV1() {
        List<Order> all = orderRepository.findByString(new OrderSearch());
        for (Order order : all) {
            order.getMember().getName(); //Lazy 강제 초기화
            order.getDelivery().getAddress(); //Lazy 강제 초기화
        }
        return all;
    }

}