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 엔티티 직접 노출
<엔티티 직접 노출>
오류발생

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;
}
}